gstbasetextoverlay.c 97.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* GStreamer
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
 * Copyright (C) <2003> David Schleef <ds@schleef.org>
 * Copyright (C) <2006> Julien Moutte <julien@moutte.net>
 * Copyright (C) <2006> Zeeshan Ali <zeeshan.ali@nokia.com>
 * Copyright (C) <2006-2008> Tim-Philipp Müller <tim centricular net>
 * Copyright (C) <2009> Young-Ho Cha <ganadist@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
21
22
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
23
24
25
26
27
28
29
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gst/video/video.h>
30
#include <gst/video/gstvideometa.h>
31
32
33
34
35
36
37

#include "gstbasetextoverlay.h"
#include "gsttextoverlay.h"
#include "gsttimeoverlay.h"
#include "gstclockoverlay.h"
#include "gsttextrender.h"
#include <string.h>
38
#include <math.h>
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

/* FIXME:
 *  - use proper strides and offset for I420
 *  - if text is wider than the video picture, it does not get
 *    clipped properly during blitting (if wrapping is disabled)
 */

GST_DEBUG_CATEGORY (pango_debug);
#define GST_CAT_DEFAULT pango_debug

#define DEFAULT_PROP_TEXT 	""
#define DEFAULT_PROP_SHADING	FALSE
#define DEFAULT_PROP_VALIGNMENT	GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
#define DEFAULT_PROP_HALIGNMENT	GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
#define DEFAULT_PROP_XPAD	25
#define DEFAULT_PROP_YPAD	25
#define DEFAULT_PROP_DELTAX	0
#define DEFAULT_PROP_DELTAY	0
#define DEFAULT_PROP_XPOS       0.5
#define DEFAULT_PROP_YPOS       0.5
#define DEFAULT_PROP_WRAP_MODE  GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
#define DEFAULT_PROP_FONT_DESC	""
#define DEFAULT_PROP_SILENT	FALSE
#define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
#define DEFAULT_PROP_WAIT_TEXT	TRUE
#define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
#define DEFAULT_PROP_VERTICAL_RENDER  FALSE
66
67
#define DEFAULT_PROP_DRAW_SHADOW TRUE
#define DEFAULT_PROP_DRAW_OUTLINE TRUE
68
#define DEFAULT_PROP_COLOR      0xffffffff
69
#define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
70
#define DEFAULT_PROP_SHADING_VALUE    80
71
72
73
74
#define DEFAULT_PROP_TEXT_X 0
#define DEFAULT_PROP_TEXT_Y 0
#define DEFAULT_PROP_TEXT_WIDTH 1
#define DEFAULT_PROP_TEXT_HEIGHT 1
75
76
77
78
79
80
81
82
83

#define MINIMUM_OUTLINE_OFFSET 1.0
#define DEFAULT_SCALE_BASIS    640

enum
{
  PROP_0,
  PROP_TEXT,
  PROP_SHADING,
84
  PROP_SHADING_VALUE,
85
86
87
88
89
90
91
92
  PROP_HALIGNMENT,
  PROP_VALIGNMENT,
  PROP_XPAD,
  PROP_YPAD,
  PROP_DELTAX,
  PROP_DELTAY,
  PROP_XPOS,
  PROP_YPOS,
93
94
  PROP_X_ABSOLUTE,
  PROP_Y_ABSOLUTE,
95
96
97
98
99
100
101
102
  PROP_WRAP_MODE,
  PROP_FONT_DESC,
  PROP_SILENT,
  PROP_LINE_ALIGNMENT,
  PROP_WAIT_TEXT,
  PROP_AUTO_ADJUST_SIZE,
  PROP_VERTICAL_RENDER,
  PROP_COLOR,
103
104
  PROP_DRAW_SHADOW,
  PROP_DRAW_OUTLINE,
105
  PROP_OUTLINE_COLOR,
106
107
108
109
  PROP_TEXT_X,
  PROP_TEXT_Y,
  PROP_TEXT_WIDTH,
  PROP_TEXT_HEIGHT,
110
111
112
  PROP_LAST
};

113
#define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
114

115
116
117
118
119
120
121
122
#define BASE_TEXT_OVERLAY_CAPS GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS)

#define BASE_TEXT_OVERLAY_ALL_CAPS BASE_TEXT_OVERLAY_CAPS ";" \
    GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)

static GstStaticCaps sw_template_caps =
GST_STATIC_CAPS (BASE_TEXT_OVERLAY_CAPS);

123
static GstStaticPadTemplate src_template_factory =
124
GST_STATIC_PAD_TEMPLATE ("src",
125
126
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
127
    GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
128
129
130
    );

static GstStaticPadTemplate video_sink_template_factory =
131
GST_STATIC_PAD_TEMPLATE ("video_sink",
132
133
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
134
    GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
135
136
137
138
139
140
141
142
143
144
145
    );

#define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
static GType
gst_base_text_overlay_valign_get_type (void)
{
  static GType base_text_overlay_valign_type = 0;
  static const GEnumValue base_text_overlay_valign[] = {
    {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
    {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
    {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
146
147
    {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position",
        "Absolute position clamped to canvas"},
148
    {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
149
    {GST_BASE_TEXT_OVERLAY_VALIGN_ABSOLUTE, "absolute", "Absolute position"},
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
    {0, NULL, NULL},
  };

  if (!base_text_overlay_valign_type) {
    base_text_overlay_valign_type =
        g_enum_register_static ("GstBaseTextOverlayVAlign",
        base_text_overlay_valign);
  }
  return base_text_overlay_valign_type;
}

#define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
static GType
gst_base_text_overlay_halign_get_type (void)
{
  static GType base_text_overlay_halign_type = 0;
  static const GEnumValue base_text_overlay_halign[] = {
    {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
    {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
    {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
170
171
172
    {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position",
        "Absolute position clamped to canvas"},
    {GST_BASE_TEXT_OVERLAY_HALIGN_ABSOLUTE, "absolute", "Absolute position"},
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
    {0, NULL, NULL},
  };

  if (!base_text_overlay_halign_type) {
    base_text_overlay_halign_type =
        g_enum_register_static ("GstBaseTextOverlayHAlign",
        base_text_overlay_halign);
  }
  return base_text_overlay_halign_type;
}


#define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
static GType
gst_base_text_overlay_wrap_mode_get_type (void)
{
  static GType base_text_overlay_wrap_mode_type = 0;
  static const GEnumValue base_text_overlay_wrap_mode[] = {
    {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
    {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
    {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
    {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
    {0, NULL, NULL},
  };

  if (!base_text_overlay_wrap_mode_type) {
    base_text_overlay_wrap_mode_type =
        g_enum_register_static ("GstBaseTextOverlayWrapMode",
        base_text_overlay_wrap_mode);
  }
  return base_text_overlay_wrap_mode_type;
}

#define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
static GType
gst_base_text_overlay_line_align_get_type (void)
{
  static GType base_text_overlay_line_align_type = 0;
  static const GEnumValue base_text_overlay_line_align[] = {
    {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
    {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
    {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
    {0, NULL, NULL}
  };

  if (!base_text_overlay_line_align_type) {
    base_text_overlay_line_align_type =
        g_enum_register_static ("GstBaseTextOverlayLineAlign",
        base_text_overlay_line_align);
  }
  return base_text_overlay_line_align_type;
}

226
227
228
229
230
231
232
#define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
#define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
#define GST_BASE_TEXT_OVERLAY_LOCK(ov)     (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
#define GST_BASE_TEXT_OVERLAY_UNLOCK(ov)   (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
#define GST_BASE_TEXT_OVERLAY_WAIT(ov)     (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
#define GST_BASE_TEXT_OVERLAY_SIGNAL(ov)   (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
#define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
233

234
235
236
237
238
239
static GstElementClass *parent_class = NULL;
static void gst_base_text_overlay_base_init (gpointer g_class);
static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
    GstBaseTextOverlayClass * klass);

240
241
242
static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
    element, GstStateChange transition);

243
244
245
static GstCaps *gst_base_text_overlay_get_videosink_caps (GstPad * pad,
    GstBaseTextOverlay * overlay, GstCaps * filter);
static GstCaps *gst_base_text_overlay_get_src_caps (GstPad * pad,
246
    GstBaseTextOverlay * overlay, GstCaps * filter);
Wim Taymans's avatar
Wim Taymans committed
247
248
249
static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
    GstCaps * caps);
static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
250
251
    GstCaps * caps);
static gboolean gst_base_text_overlay_src_event (GstPad * pad,
Wim Taymans's avatar
Wim Taymans committed
252
    GstObject * parent, GstEvent * event);
253
static gboolean gst_base_text_overlay_src_query (GstPad * pad,
Wim Taymans's avatar
Wim Taymans committed
254
    GstObject * parent, GstQuery * query);
255
256

static gboolean gst_base_text_overlay_video_event (GstPad * pad,
Wim Taymans's avatar
Wim Taymans committed
257
    GstObject * parent, GstEvent * event);
Wim Taymans's avatar
Wim Taymans committed
258
static gboolean gst_base_text_overlay_video_query (GstPad * pad,
Wim Taymans's avatar
Wim Taymans committed
259
    GstObject * parent, GstQuery * query);
260
static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
Wim Taymans's avatar
Wim Taymans committed
261
    GstObject * parent, GstBuffer * buffer);
262
263

static gboolean gst_base_text_overlay_text_event (GstPad * pad,
Wim Taymans's avatar
Wim Taymans committed
264
    GstObject * parent, GstEvent * event);
265
static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
Wim Taymans's avatar
Wim Taymans committed
266
    GstObject * parent, GstBuffer * buffer);
267
static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
268
269
270
    GstObject * parent, GstPad * peer);
static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
    GstObject * parent);
271
272
273
274
275
276
277
static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);

static void gst_base_text_overlay_finalize (GObject * object);
static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
278

279
280
281
static void
gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
    PangoFontDescription * desc);
282
static gboolean gst_base_text_overlay_can_handle_caps (GstCaps * incaps);
283

284
285
286
static void
gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay);

287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
GType
gst_base_text_overlay_get_type (void)
{
  static GType type = 0;

  if (g_once_init_enter ((gsize *) & type)) {
    static const GTypeInfo info = {
      sizeof (GstBaseTextOverlayClass),
      (GBaseInitFunc) gst_base_text_overlay_base_init,
      NULL,
      (GClassInitFunc) gst_base_text_overlay_class_init,
      NULL,
      NULL,
      sizeof (GstBaseTextOverlay),
      0,
      (GInstanceInitFunc) gst_base_text_overlay_init,
    };

    g_once_init_leave ((gsize *) & type,
        g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
            0));
  }

  return type;
}
312
313
314
315
316
317
318
319
320
321
322

static gchar *
gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
    GstBuffer * video_frame)
{
  return g_strdup (overlay->default_text);
}

static void
gst_base_text_overlay_base_init (gpointer g_class)
{
323
324
325
326
327
328
329
330
331
332
  GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
  PangoFontMap *fontmap;

  /* Only lock for the subclasses here, the base class
   * doesn't have this mutex yet and it's not necessary
   * here */
  if (klass->pango_lock)
    g_mutex_lock (klass->pango_lock);
  fontmap = pango_cairo_font_map_get_default ();
  klass->pango_context =
333
      pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
334
  pango_context_set_base_gravity (klass->pango_context, PANGO_GRAVITY_SOUTH);
335
336
  if (klass->pango_lock)
    g_mutex_unlock (klass->pango_lock);
337
338
339
340
341
342
343
344
345
346
347
}

static void
gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;

348
349
  parent_class = g_type_class_peek_parent (klass);

350
351
352
353
  gobject_class->finalize = gst_base_text_overlay_finalize;
  gobject_class->set_property = gst_base_text_overlay_set_property;
  gobject_class->get_property = gst_base_text_overlay_get_property;

354
355
356
357
  gst_element_class_add_static_pad_template (gstelement_class,
      &src_template_factory);
  gst_element_class_add_static_pad_template (gstelement_class,
      &video_sink_template_factory);
358
359
360
361

  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);

362
363
  klass->pango_lock = g_slice_new (GMutex);
  g_mutex_init (klass->pango_lock);
364
365
366
367
368
369
370
371
372
373
374

  klass->get_text = gst_base_text_overlay_get_text;

  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
      g_param_spec_string ("text", "text",
          "Text to be display.", DEFAULT_PROP_TEXT,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
      g_param_spec_boolean ("shaded-background", "shaded background",
          "Whether to shade the background under the text area",
          DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
375
376
377
378
379
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING_VALUE,
      g_param_spec_uint ("shading-value", "background shading value",
          "Shading value to apply if shaded-background is true", 1, 255,
          DEFAULT_PROP_SHADING_VALUE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
      g_param_spec_enum ("valignment", "vertical alignment",
          "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
          DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
      g_param_spec_enum ("halignment", "horizontal alignment",
          "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
          DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
      g_param_spec_int ("xpad", "horizontal paddding",
          "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
          DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
      g_param_spec_int ("ypad", "vertical padding",
          "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
          DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
      g_param_spec_int ("deltax", "X position modifier",
          "Shift X position to the left or to the right. Unit is pixels.",
          G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
400
          GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
401
402
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
      g_param_spec_int ("deltay", "Y position modifier",
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
          "Shift Y position up or down. Unit is pixels.",
          G_MININT, G_MAXINT, DEFAULT_PROP_DELTAY,
          GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstBaseTextOverlay:text-x:
   *
   * Resulting X position of font rendering.
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_X,
      g_param_spec_int ("text-x", "horizontal position.",
          "Resulting X position of font rendering.", -G_MAXINT,
          G_MAXINT, DEFAULT_PROP_TEXT_X, G_PARAM_READABLE));

  /**
   * GstBaseTextOverlay:text-y:
   *
   * Resulting Y position of font rendering.
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_Y,
      g_param_spec_int ("text-y", "vertical position",
          "Resulting X position of font rendering.", -G_MAXINT,
          G_MAXINT, DEFAULT_PROP_TEXT_Y, G_PARAM_READABLE));

  /**
   * GstBaseTextOverlay:text-width:
   *
   * Resulting width of font rendering.
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_WIDTH,
      g_param_spec_uint ("text-width", "width",
          "Resulting width of font rendering",
          0, G_MAXINT, DEFAULT_PROP_TEXT_WIDTH, G_PARAM_READABLE));

  /**
   * GstBaseTextOverlay:text-height:
   *
   * Resulting height of font rendering.
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_HEIGHT,
      g_param_spec_uint ("text-height", "height",
          "Resulting height of font rendering", 0,
          G_MAXINT, DEFAULT_PROP_TEXT_HEIGHT, G_PARAM_READABLE));

447
  /**
448
   * GstBaseTextOverlay:xpos:
449
450
   *
   * Horizontal position of the rendered text when using positioned alignment.
451
   */
452
453
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
      g_param_spec_double ("xpos", "horizontal position",
454
          "Horizontal position when using clamped position alignment", 0, 1.0,
455
          DEFAULT_PROP_XPOS,
456
457
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  /**
458
   * GstBaseTextOverlay:ypos:
459
460
   *
   * Vertical position of the rendered text when using positioned alignment.
461
   */
462
463
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
      g_param_spec_double ("ypos", "vertical position",
464
          "Vertical position when using clamped position alignment", 0, 1.0,
465
          DEFAULT_PROP_YPOS,
466
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502

  /**
   * GstBaseTextOverlay:x-absolute:
   *
   * Horizontal position of the rendered text when using absolute alignment.
   *
   * Maps the text area to be exactly inside of video canvas for [0, 0] - [1, 1]:
   *
   * [0, 0]: Top-Lefts of video and text are aligned
   * [0.5, 0.5]: Centers are aligned
   * [1, 1]: Bottom-Rights are aligned
   *
   * Values beyond [0, 0] - [1, 1] place the text outside of the video canvas.
   *
   * Since: 1.8
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X_ABSOLUTE,
      g_param_spec_double ("x-absolute", "horizontal position",
          "Horizontal position when using absolute alignment", -G_MAXDOUBLE,
          G_MAXDOUBLE, DEFAULT_PROP_XPOS,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  /**
   * GstBaseTextOverlay:y-absolute:
   *
   * See x-absolute.
   *
   * Vertical position of the rendered text when using absolute alignment.
   *
   * Since: 1.8
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y_ABSOLUTE,
      g_param_spec_double ("y-absolute", "vertical position",
          "Vertical position when using absolute alignment", -G_MAXDOUBLE,
          G_MAXDOUBLE, DEFAULT_PROP_YPOS,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));

503
504
505
506
507
508
509
510
511
512
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
      g_param_spec_enum ("wrap-mode", "wrap mode",
          "Whether to wrap the text and if so how.",
          GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
      g_param_spec_string ("font-desc", "font description",
          "Pango font description of font to be used for rendering. "
          "See documentation of pango_font_description_from_string "
          "for syntax.", DEFAULT_PROP_FONT_DESC,
513
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
514
  /**
515
   * GstBaseTextOverlay:color:
516
517
   *
   * Color of the rendered text.
518
   */
519
520
521
522
523
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
      g_param_spec_uint ("color", "Color",
          "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
          DEFAULT_PROP_COLOR,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
524
  /**
525
   * GstTextOverlay:outline-color:
526
527
   *
   * Color of the outline of the rendered text.
528
   */
529
530
531
532
533
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
      g_param_spec_uint ("outline-color", "Text Outline Color",
          "Color to use for outline the text (big-endian ARGB).", 0,
          G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
534
535

  /**
536
   * GstBaseTextOverlay:line-alignment:
537
538
   *
   * Alignment of text lines relative to each other (for multi-line text)
539
   */
540
541
542
543
544
545
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
      g_param_spec_enum ("line-alignment", "line alignment",
          "Alignment of text lines relative to each other.",
          GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
546
   * GstBaseTextOverlay:silent:
547
548
549
   *
   * If set, no text is rendered. Useful to switch off text rendering
   * temporarily without removing the textoverlay element from the pipeline.
550
   */
551
552
553
554
555
556
  /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
      g_param_spec_boolean ("silent", "silent",
          "Whether to render the text string",
          DEFAULT_PROP_SILENT,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
  /**
   * GstBaseTextOverlay:draw-shadow:
   *
   * If set, a text shadow is drawn.
   *
   * Since: 1.6
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_SHADOW,
      g_param_spec_boolean ("draw-shadow", "draw-shadow",
          "Whether to draw shadow",
          DEFAULT_PROP_DRAW_SHADOW,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstBaseTextOverlay:draw-outline:
   *
   * If set, an outline is drawn.
   *
   * Since: 1.6
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_OUTLINE,
      g_param_spec_boolean ("draw-outline", "draw-outline",
          "Whether to draw outline",
          DEFAULT_PROP_DRAW_OUTLINE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
581
  /**
582
   * GstBaseTextOverlay:wait-text:
583
584
585
586
   *
   * If set, the video will block until a subtitle is received on the text pad.
   * If video and subtitles are sent in sync, like from the same demuxer, this
   * property should be set.
587
   */
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
      g_param_spec_boolean ("wait-text", "Wait Text",
          "Whether to wait for subtitles",
          DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (G_OBJECT_CLASS (klass),
      PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
          "Automatically adjust font size to screen-size.",
          DEFAULT_PROP_AUTO_ADJUST_SIZE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
      g_param_spec_boolean ("vertical-render", "vertical render",
          "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static void
gst_base_text_overlay_finalize (GObject * object)
{
  GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);

  g_free (overlay->default_text);

612
613
614
615
616
  if (overlay->composition) {
    gst_video_overlay_composition_unref (overlay->composition);
    overlay->composition = NULL;
  }

617
  if (overlay->text_image) {
618
    gst_buffer_unref (overlay->text_image);
619
620
621
622
623
624
625
626
627
628
629
630
631
    overlay->text_image = NULL;
  }

  if (overlay->layout) {
    g_object_unref (overlay->layout);
    overlay->layout = NULL;
  }

  if (overlay->text_buffer) {
    gst_buffer_unref (overlay->text_buffer);
    overlay->text_buffer = NULL;
  }

632
  g_mutex_clear (&overlay->lock);
633
  g_cond_clear (&overlay->cond);
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
    GstBaseTextOverlayClass * klass)
{
  GstPadTemplate *template;
  PangoFontDescription *desc;

  /* video sink */
  template = gst_static_pad_template_get (&video_sink_template_factory);
  overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
  gst_object_unref (template);
  gst_pad_set_event_function (overlay->video_sinkpad,
      GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
  gst_pad_set_chain_function (overlay->video_sinkpad,
      GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
Wim Taymans's avatar
Wim Taymans committed
653
654
  gst_pad_set_query_function (overlay->video_sinkpad,
      GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
655
  GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
  gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);

  template =
      gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
      "text_sink");
  if (template) {
    /* text sink */
    overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");

    gst_pad_set_event_function (overlay->text_sinkpad,
        GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
    gst_pad_set_chain_function (overlay->text_sinkpad,
        GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
    gst_pad_set_link_function (overlay->text_sinkpad,
        GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
    gst_pad_set_unlink_function (overlay->text_sinkpad,
        GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
    gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
  }

  /* (video) source */
  template = gst_static_pad_template_get (&src_template_factory);
  overlay->srcpad = gst_pad_new_from_template (template, "src");
  gst_object_unref (template);
  gst_pad_set_event_function (overlay->srcpad,
      GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
  gst_pad_set_query_function (overlay->srcpad,
      GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
  gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);

686
  g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
687
688
689
690
691
692
693
694
695
  overlay->layout =
      pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
      (overlay)->pango_context);
  desc =
      pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
      (overlay)->pango_context);
  gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);

  overlay->color = DEFAULT_PROP_COLOR;
696
  overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
697
698
699
700
701
702
703
704
705
706
707
708
  overlay->halign = DEFAULT_PROP_HALIGNMENT;
  overlay->valign = DEFAULT_PROP_VALIGNMENT;
  overlay->xpad = DEFAULT_PROP_XPAD;
  overlay->ypad = DEFAULT_PROP_YPAD;
  overlay->deltax = DEFAULT_PROP_DELTAX;
  overlay->deltay = DEFAULT_PROP_DELTAY;
  overlay->xpos = DEFAULT_PROP_XPOS;
  overlay->ypos = DEFAULT_PROP_YPOS;

  overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;

  overlay->want_shading = DEFAULT_PROP_SHADING;
709
  overlay->shading_value = DEFAULT_PROP_SHADING_VALUE;
710
  overlay->silent = DEFAULT_PROP_SILENT;
711
712
  overlay->draw_shadow = DEFAULT_PROP_DRAW_SHADOW;
  overlay->draw_outline = DEFAULT_PROP_DRAW_OUTLINE;
713
714
715
716
717
718
719
  overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
  overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;

  overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
  overlay->need_render = TRUE;
  overlay->text_image = NULL;
  overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
720
721
722
723

  overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
  pango_layout_set_alignment (overlay->layout,
      (PangoAlignment) overlay->line_align);
724
725
726

  overlay->text_buffer = NULL;
  overlay->text_linked = FALSE;
727

728
729
730
  overlay->composition = NULL;
  overlay->upstream_composition = NULL;

731
732
733
734
735
736
  overlay->width = 1;
  overlay->height = 1;

  overlay->window_width = 1;
  overlay->window_height = 1;

737
738
739
740
741
  overlay->text_width = DEFAULT_PROP_TEXT_WIDTH;
  overlay->text_height = DEFAULT_PROP_TEXT_HEIGHT;

  overlay->text_x = DEFAULT_PROP_TEXT_X;
  overlay->text_y = DEFAULT_PROP_TEXT_Y;
742

743
744
745
746
  overlay->render_width = 1;
  overlay->render_height = 1;
  overlay->render_scale = 1.0l;

747
  g_mutex_init (&overlay->lock);
748
  g_cond_init (&overlay->cond);
749
  gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
750
  g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
751
752
753
}

static void
754
gst_base_text_overlay_set_wrap_mode (GstBaseTextOverlay * overlay, gint width)
755
756
757
758
759
{
  if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
    GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
    pango_layout_set_width (overlay->layout, -1);
  } else {
760
    width = width * PANGO_SCALE;
761

762
    GST_DEBUG_OBJECT (overlay, "Set layout width %d", width);
763
764
765
766
    GST_DEBUG_OBJECT (overlay, "Set wrap mode    %d", overlay->wrap_mode);
    pango_layout_set_width (overlay->layout, width);
  }

767
  pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
768
769
770
}

static gboolean
Wim Taymans's avatar
Wim Taymans committed
771
gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
772
773
{
  GstStructure *structure;
774
  const gchar *format;
775
776

  structure = gst_caps_get_structure (caps, 0);
777
778
  format = gst_structure_get_string (structure, "format");
  overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
779
780
781
782

  return TRUE;
}

783
784
/* only negotiate/query video overlay composition support for now */
static gboolean
785
gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay, GstCaps * caps)
786
{
787
  gboolean upstream_has_meta = FALSE;
788
789
  gboolean caps_has_meta = FALSE;
  gboolean alloc_has_meta = FALSE;
790
  gboolean attach = FALSE;
791
  gboolean ret = TRUE;
792
  guint width, height;
793
  GstCapsFeatures *f;
794
  GstCaps *overlay_caps;
795
796
  GstQuery *query;
  guint alloc_index;
797
798
799

  GST_DEBUG_OBJECT (overlay, "performing negotiation");

800
801
802
  /* Clear any pending reconfigure to avoid negotiating twice */
  gst_pad_check_reconfigure (overlay->srcpad);

803
804
805
806
  if (!caps)
    caps = gst_pad_get_current_caps (overlay->video_sinkpad);
  else
    gst_caps_ref (caps);
807

808
  if (!caps || gst_caps_is_empty (caps))
809
810
    goto no_format;

811
812
  /* Check if upstream caps have meta */
  if ((f = gst_caps_get_features (caps, 0))) {
813
    upstream_has_meta = gst_caps_features_contains (f,
814
        GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
815
  }
816

817
  /* Initialize dimensions */
818
819
  width = overlay->width;
  height = overlay->height;
820

821
822
823
  if (upstream_has_meta) {
    overlay_caps = gst_caps_ref (caps);
  } else {
824
825
    GstCaps *peercaps;

826
    /* BaseTransform requires caps for the allocation query to work */
827
828
    overlay_caps = gst_caps_copy (caps);
    f = gst_caps_get_features (overlay_caps, 0);
829
830
831
    gst_caps_features_add (f,
        GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);

832
    /* Then check if downstream accept overlay composition in caps */
833
834
835
836
837
838
839
840
    /* FIXME: We should probably check if downstream *prefers* the
     * overlay meta, and only enforce usage of it if we can't handle
     * the format ourselves and thus would have to drop the overlays.
     * Otherwise we should prefer what downstream wants here.
     */
    peercaps = gst_pad_peer_query_caps (overlay->srcpad, NULL);
    caps_has_meta = gst_caps_can_intersect (peercaps, overlay_caps);
    gst_caps_unref (peercaps);
841

842
843
    GST_DEBUG ("caps have overlay meta %d", caps_has_meta);
  }
844

845
846
847
848
  if (upstream_has_meta || caps_has_meta) {
    /* Send caps immediatly, it's needed by GstBaseTransform to get a reply
     * from allocation query */
    ret = gst_pad_set_caps (overlay->srcpad, overlay_caps);
849

850
851
    /* First check if the allocation meta has compositon */
    query = gst_query_new_allocation (overlay_caps, FALSE);
852

853
854
855
    if (!gst_pad_peer_query (overlay->srcpad, query)) {
      /* no problem, we use the query defaults */
      GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
856

857
858
859
860
861
      /* In case we were flushing, mark reconfigure and fail this method,
       * will make it retry */
      if (overlay->video_flushing)
        ret = FALSE;
    }
862

863
864
    alloc_has_meta = gst_query_find_allocation_meta (query,
        GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, &alloc_index);
865

866
    GST_DEBUG ("sink alloc has overlay meta %d", alloc_has_meta);
867

868
869
870
871
872
873
874
875
876
877
    if (alloc_has_meta) {
      const GstStructure *params;

      gst_query_parse_nth_allocation_meta (query, alloc_index, &params);
      if (params) {
        if (gst_structure_get (params, "width", G_TYPE_UINT, &width,
                "height", G_TYPE_UINT, &height, NULL)) {
          GST_DEBUG ("received window size: %dx%d", width, height);
          g_assert (width != 0 && height != 0);
        }
878
879
      }
    }
880
881

    gst_query_unref (query);
882
  }
883

884
  /* Update render size if needed */
885
886
887
  overlay->window_width = width;
  overlay->window_height = height;
  gst_base_text_overlay_update_render_size (overlay);
888

889
890
891
892
  /* For backward compatbility, we will prefer bliting if downstream
   * allocation does not support the meta. In other case we will prefer
   * attaching, and will fail the negotiation in the unlikely case we are
   * force to blit, but format isn't supported. */
893

894
895
896
  if (upstream_has_meta) {
    attach = TRUE;
  } else if (caps_has_meta) {
897
898
899
900
901
    if (alloc_has_meta) {
      attach = TRUE;
    } else {
      /* Don't attach unless we cannot handle the format */
      attach = !gst_base_text_overlay_can_handle_caps (caps);
902
    }
903
904
905
906
907
908
  } else {
    ret = gst_base_text_overlay_can_handle_caps (caps);
  }

  /* If we attach, then pick the overlay caps */
  if (attach) {
909
910
911
    GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, overlay_caps);
    /* Caps where already sent */
  } else if (ret) {
912
913
    GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, caps);
    ret = gst_pad_set_caps (overlay->srcpad, caps);
914
915
  }

916
917
  overlay->attach_compo_to_buffer = attach;

918
919
920
921
922
  if (!ret) {
    GST_DEBUG_OBJECT (overlay, "negotiation failed, schedule reconfigure");
    gst_pad_mark_reconfigure (overlay->srcpad);
  }

923
  gst_caps_unref (overlay_caps);
924
925
926
  gst_caps_unref (caps);

  return ret;
927
928
929

no_format:
  {
930
931
    if (caps)
      gst_caps_unref (caps);
932
933
934
935
    return FALSE;
  }
}

936
937
938
939
940
static gboolean
gst_base_text_overlay_can_handle_caps (GstCaps * incaps)
{
  gboolean ret;
  GstCaps *caps;
941
  static GstStaticCaps static_caps = GST_STATIC_CAPS (BASE_TEXT_OVERLAY_CAPS);
942
943
944
945
946
947
948
949

  caps = gst_static_caps_get (&static_caps);
  ret = gst_caps_is_subset (incaps, caps);
  gst_caps_unref (caps);

  return ret;
}

950
static gboolean
Wim Taymans's avatar
Wim Taymans committed
951
gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
952
{
Wim Taymans's avatar
Wim Taymans committed
953
  GstVideoInfo info;
954
955
  gboolean ret = FALSE;

Wim Taymans's avatar
Wim Taymans committed
956
957
  if (!gst_video_info_from_caps (&info, caps))
    goto invalid_caps;
958

959
960
961
962
963
  /* Render again if size have changed */
  if (GST_VIDEO_INFO_WIDTH (&info) != GST_VIDEO_INFO_WIDTH (&overlay->info) ||
      GST_VIDEO_INFO_HEIGHT (&info) != GST_VIDEO_INFO_HEIGHT (&overlay->info))
    overlay->need_render = TRUE;

Wim Taymans's avatar
Wim Taymans committed
964
  overlay->info = info;
965
  overlay->format = GST_VIDEO_INFO_FORMAT (&info);
966
967
  overlay->width = GST_VIDEO_INFO_WIDTH (&info);
  overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
968

969
  ret = gst_base_text_overlay_negotiate (overlay, caps);
970

971
  GST_BASE_TEXT_OVERLAY_LOCK (overlay);
972

973
974
975
976
  if (!overlay->attach_compo_to_buffer &&
      !gst_base_text_overlay_can_handle_caps (caps)) {
    GST_DEBUG_OBJECT (overlay, "unsupported caps %" GST_PTR_FORMAT, caps);
    ret = FALSE;
977
  }
978
979
  GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);

980
  return ret;
Wim Taymans's avatar
Wim Taymans committed
981
982
983
984
985
986
987

  /* ERRORS */
invalid_caps:
  {
    GST_DEBUG_OBJECT (overlay, "could not parse caps");
    return FALSE;
  }
988
989
990
991
992
993
994
995
}

static void
gst_base_text_overlay_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);

996
  GST_BASE_TEXT_OVERLAY_LOCK (overlay);
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
  switch (prop_id) {
    case PROP_TEXT:
      g_free (overlay->default_text);
      overlay->default_text = g_value_dup_string (value);
      break;
    case PROP_SHADING:
      overlay->want_shading = g_value_get_boolean (value);
      break;
    case PROP_XPAD:
      overlay->xpad = g_value_get_int (value);
      break;
    case PROP_YPAD:
      overlay->ypad = g_value_get_int (value);
      break;
    case PROP_DELTAX:
      overlay->deltax = g_value_get_int (value);
      break;
    case PROP_DELTAY:
      overlay->deltay = g_value_get_int (value);
      break;
    case PROP_XPOS:
      overlay->xpos = g_value_get_double (value);
      break;
    case PROP_YPOS:
      overlay->ypos = g_value_get_double (value);
      break;
1023
1024
1025
1026
1027
1028
    case PROP_X_ABSOLUTE:
      overlay->xpos = g_value_get_double (value);
      break;
    case PROP_Y_ABSOLUTE:
      overlay->ypos = g_value_get_double (value);
      break;
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
    case PROP_VALIGNMENT:
      overlay->valign = g_value_get_enum (value);
      break;
    case PROP_HALIGNMENT:
      overlay->halign = g_value_get_enum (value);
      break;
    case PROP_WRAP_MODE:
      overlay->wrap_mode = g_value_get_enum (value);
      break;
    case PROP_FONT_DESC:
    {
      PangoFontDescription *desc;
      const gchar *fontdesc_str;

      fontdesc_str = g_value_get_string (value);
1044
      g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
      desc = pango_font_description_from_string (fontdesc_str);
      if (desc) {
        GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
        pango_layout_set_font_description (overlay->layout, desc);
        gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
        pango_font_description_free (desc);
      } else {
        GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
            fontdesc_str);
      }
1055
      g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1056
1057
1058
1059
1060
      break;
    }
    case PROP_COLOR:
      overlay->color = g_value_get_uint (value);
      break;
1061
1062
1063
    case PROP_OUTLINE_COLOR:
      overlay->outline_color = g_value_get_uint (value);
      break;
1064
1065
1066
    case PROP_SILENT:
      overlay->silent = g_value_get_boolean (value);
      break;
1067
1068
1069
1070
1071
1072
    case PROP_DRAW_SHADOW:
      overlay->draw_shadow = g_value_get_boolean (value);
      break;
    case PROP_DRAW_OUTLINE:
      overlay->draw_outline = g_value_get_boolean (value);
      break;
1073
1074
    case PROP_LINE_ALIGNMENT:
      overlay->line_align = g_value_get_enum (value);
1075
      g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1076
1077
      pango_layout_set_alignment (overlay->layout,
          (PangoAlignment) overlay->line_align);
1078
      g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1079
1080
1081
1082
1083
1084
1085
1086
1087
      break;
    case PROP_WAIT_TEXT:
      overlay->wait_text = g_value_get_boolean (value);
      break;
    case PROP_AUTO_ADJUST_SIZE:
      overlay->auto_adjust_size = g_value_get_boolean (value);
      break;
    case PROP_VERTICAL_RENDER:
      overlay->use_vertical_render = g_value_get_boolean (value);
1088
1089
1090
1091
1092
1093
1094
1095
1096
      if (overlay->use_vertical_render) {
        overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
        overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
        overlay->line_align = GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT;
        g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
        pango_layout_set_alignment (overlay->layout,
            (PangoAlignment) overlay->line_align);
        g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
      }
1097
      break;
1098
1099
1100
    case PROP_SHADING_VALUE:
      overlay->shading_value = g_value_get_uint (value);
      break;
1101
1102
1103
1104
1105
1106
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }

  overlay->need_render = TRUE;
1107
  GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1108
1109
1110
1111
1112
1113
1114
1115
}

static void
gst_base_text_overlay_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);

1116
  GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
  switch (prop_id) {
    case PROP_TEXT:
      g_value_set_string (value, overlay->default_text);
      break;
    case PROP_SHADING:
      g_value_set_boolean (value, overlay->want_shading);
      break;
    case PROP_XPAD:
      g_value_set_int (value, overlay->xpad);
      break;
    case PROP_YPAD:
      g_value_set_int (value, overlay->ypad);
      break;
    case PROP_DELTAX:
      g_value_set_int (value, overlay->deltax);
      break;
    case PROP_DELTAY:
      g_value_set_int (value, overlay->deltay);
      break;
    case PROP_XPOS:
      g_value_set_double (value, overlay->xpos);
      break;
    case PROP_YPOS:
      g_value_set_double (value, overlay->ypos);
      break;
1142
1143
1144
1145
1146
1147
    case PROP_X_ABSOLUTE:
      g_value_set_double (value, overlay->xpos);
      break;
    case PROP_Y_ABSOLUTE:
      g_value_set_double (value, overlay->ypos);
      break;
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
    case PROP_VALIGNMENT:
      g_value_set_enum (value, overlay->valign);
      break;
    case PROP_HALIGNMENT:
      g_value_set_enum (value, overlay->halign);
      break;
    case PROP_WRAP_MODE:
      g_value_set_enum (value, overlay->wrap_mode);
      break;
    case PROP_SILENT:
      g_value_set_boolean (value, overlay->silent);
      break;
1160
1161
1162
1163
1164
1165
    case PROP_DRAW_SHADOW:
      g_value_set_boolean (value, overlay->draw_shadow);
      break;
    case PROP_DRAW_OUTLINE:
      g_value_set_boolean (value, overlay->draw_outline);
      break;
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
    case PROP_LINE_ALIGNMENT:
      g_value_set_enum (value, overlay->line_align);
      break;
    case PROP_WAIT_TEXT:
      g_value_set_boolean (value, overlay->wait_text);
      break;
    case PROP_AUTO_ADJUST_SIZE:
      g_value_set_boolean (value, overlay->auto_adjust_size);
      break;
    case PROP_VERTICAL_RENDER:
      g_value_set_boolean (value, overlay->use_vertical_render);
      break;
    case PROP_COLOR:
      g_value_set_uint (value, overlay->color);
      break;
1181
1182
1183
    case PROP_OUTLINE_COLOR:
      g_value_set_uint (value, overlay->outline_color);
      break;
1184
1185
1186
    case PROP_SHADING_VALUE:
      g_value_set_uint (value, overlay->shading_value);
      break;
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
    case PROP_FONT_DESC:
    {
      const PangoFontDescription *desc;

      g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
      desc = pango_layout_get_font_description (overlay->layout);
      if (!desc)
        g_value_set_string (value, "");
      else {
        g_value_take_string (value, pango_font_description_to_string (desc));
      }
      g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
      break;
    }
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
    case PROP_TEXT_X:
      g_value_set_int (value, overlay->text_x);
      break;
    case PROP_TEXT_Y:
      g_value_set_int (value, overlay->text_y);
      break;
    case PROP_TEXT_WIDTH:
      g_value_set_uint (value, overlay->text_width);
      break;
    case PROP_TEXT_HEIGHT:
      g_value_set_uint (value, overlay->text_height);
      break;
1213
1214
1215
1216
1217
1218
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }

  overlay->need_render = TRUE;
1219
  GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1220
1221
1222
}

static gboolean
Wim Taymans's avatar
Wim Taymans committed
1223
1224
gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
    GstQuery * query)
1225
1226
{
  gboolean ret = FALSE;
1227
  GstBaseTextOverlay *overlay;
1228

Wim Taymans's avatar
Wim Taymans committed
1229
  overlay = GST_BASE_TEXT_OVERLAY (parent);
1230

Wim Taymans's avatar
Wim Taymans committed
1231
1232
1233
1234
1235
1236
  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_CAPS:
    {
      GstCaps *filter, *caps;

      gst_query_parse_caps (query, &filter);
1237
      caps = gst_base_text_overlay_get_src_caps (pad, overlay, filter);
Wim Taymans's avatar
Wim Taymans committed
1238
1239
1240
1241
1242
1243
      gst_query_set_caps_result (query, caps);
      gst_caps_unref (caps);
      ret = TRUE;
      break;
    }
    default:
1244
      ret = gst_pad_query_default (pad, parent, query);
Wim Taymans's avatar
Wim Taymans committed
1245
1246
      break;
  }
1247
1248
1249
1250

  return ret;
}

1251
1252
1253
static void
gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay)
{
1254
1255
1256
  gdouble video_aspect = (gdouble) overlay->width / (gdouble) overlay->height;
  gdouble window_aspect = (gdouble) overlay->window_width /
      (gdouble) overlay->window_height;
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268

  guint text_buffer_width = 0;
  guint text_buffer_height = 0;

  if (video_aspect >= window_aspect) {
    text_buffer_width = overlay->window_width;
    text_buffer_height = window_aspect * overlay->window_height / video_aspect;
  } else if (video_aspect < window_aspect) {
    text_buffer_width = video_aspect * overlay->window_width / window_aspect;
    text_buffer_height = overlay->window_height;
  }

1269
1270
1271
  if ((overlay->render_width == text_buffer_width) &&
      (overlay->render_height == text_buffer_height))
    return;
1272

1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
  overlay->need_render = TRUE;
  overlay->render_width = text_buffer_width;
  overlay->render_height = text_buffer_height;
  overlay->render_scale = (gdouble) overlay->render_width /
      (gdouble) overlay->width;

  GST_DEBUG ("updating render dimensions %dx%d from stream %dx%d, window %dx%d "
      "and render scale %f", overlay->render_width, overlay->render_height,
      overlay->width, overlay->height, overlay->window_width,
      overlay->window_height, overlay->render_scale);
1283
1284
}

1285
static gboolean
Wim Taymans's avatar
Wim Taymans committed
1286
1287
gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
    GstEvent * event)
1288
{
1289
1290
  GstBaseTextOverlay *overlay;
  gboolean ret;
1291

Wim Taymans's avatar
Wim Taymans committed
1292
  overlay = GST_BASE_TEXT_OVERLAY (parent);
1293

1294
1295
1296
1297
1298
1299
  if (overlay->text_linked) {
    gst_event_ref (event);
    ret = gst_pad_push_event (overlay->video_sinkpad, event);
    gst_pad_push_event (overlay->text_sinkpad, event);
  } else {
    ret = gst_pad_push_event (overlay->video_sinkpad, event);
1300
1301
1302
1303
1304
  }

  return ret;
}

1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
/**
 * gst_base_text_overlay_add_feature_and_intersect:
 *
 * Creates a new #GstCaps containing the (given caps +
 * given caps feature) + (given caps intersected by the
 * given filter).
 *
 * Returns: the new #GstCaps
 */
static GstCaps *
gst_base_text_overlay_add_feature_and_intersect (GstCaps * caps,
    const gchar * feature, GstCaps * filter)
{
  int i, caps_size;
  GstCaps *new_caps;

  new_caps = gst_caps_copy (caps);

  caps_size = gst_caps_get_size (new_caps);
  for (i = 0; i < caps_size; i++) {
    GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
1326
1327
1328
1329

    if (!gst_caps_features_is_any (features)) {
      gst_caps_features_add (features, feature);
    }
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
  }

  gst_caps_append (new_caps, gst_caps_intersect_full (caps,
          filter, GST_CAPS_INTERSECT_FIRST));

  return new_caps;
}

/**
 * gst_base_text_overlay_intersect_by_feature:
 *
 * Creates a new #GstCaps based on the following filtering rule.
 *
 * For each individual caps contained in given caps, if the
 * caps uses the given caps feature, keep a version of the caps
 * with the feature and an another one without. Otherwise, intersect
 * the caps with the given filter.
 *
 * Returns: the new #GstCaps
 */
static GstCaps *
gst_base_text_overlay_intersect_by_feature (GstCaps * caps,
    const gchar * feature, GstCaps * filter)
{
  int i, caps_size;
  GstCaps *new_caps;

  new_caps = gst_caps_new_empty ();

  caps_size = gst_caps_get_size (caps);
  for (i = 0; i < caps_size; i++) {
    GstStructure *caps_structure = gst_caps_get_structure (caps, i);
    GstCapsFeatures *caps_features =
        gst_caps_features_copy (gst_caps_get_features (caps, i));
    GstCaps *filtered_caps;
    GstCaps *simple_caps =
        gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
    gst_caps_set_features (simple_caps, 0, caps_features);

    if (gst_caps_features_contains (caps_features, feature)) {
      gst_caps_append (new_caps, gst_caps_copy (simple_caps));

      gst_caps_features_remove (caps_features, feature);
      filtered_caps = gst_caps_ref (simple_caps);
    } else {
      filtered_caps = gst_caps_intersect_full (simple_caps, filter,
          GST_CAPS_INTERSECT_FIRST);
    }

    gst_caps_unref (simple_caps);
    gst_caps_append (new_caps, filtered_caps);
  }

  return new_caps;
}

static GstCaps *
gst_base_text_overlay_get_videosink_caps (GstPad * pad,
    GstBaseTextOverlay * overlay, GstCaps * filter)
{
  GstPad *srcpad = overlay->srcpad;
  GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;

  if (G_UNLIKELY (!overlay))
    return gst_pad_get_pad_template_caps (pad);

  if (filter) {
    /* filter caps + composition feature + filter caps
     * filtered by the software caps. */
    GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
    overlay_filter = gst_base_text_overlay_add_feature_and_intersect (filter,
        GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
    gst_caps_unref (sw_caps);

    GST_DEBUG_OBJECT (overlay, "overlay filter %" GST_PTR_FORMAT,
        overlay_filter);
  }

  peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);

  if (overlay_filter)
    gst_caps_unref (overlay_filter);

  if (peer_caps) {

    GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);

    if (gst_caps_is_any (peer_caps)) {
      /* if peer returns ANY caps, return filtered src pad template caps */
      caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
    } else {

      /* duplicate caps which contai