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 * Copyright (C) <2015> British Broadcasting Corporation <dash@rd.bbc.co.uk>
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
19 *
20 * You should have received a copy of the GNU Library General Public
21 * License along with this library; if not, write to the
22 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25
26 /**
27 * SECTION:element-ttmlrender
28 * @title: ttmlrender
29 *
30 * Renders timed text on top of a video stream. It receives text in buffers
31 * from a ttmlparse element; each text string is in its own #GstMemory within
32 * the GstBuffer, and the styling and layout associated with each text string
33 * is in metadata attached to the #GstBuffer.
34 *
35 * ## Example launch lines
36 * |[
37 * gst-launch-1.0 filesrc location=<media file location> ! video/quicktime ! qtdemux name=q ttmlrender name=r q. ! queue ! h264parse ! avdec_h264 ! autovideoconvert ! r.video_sink filesrc location=<subtitle file location> blocksize=16777216 ! queue ! ttmlparse ! r.text_sink r. ! ximagesink q. ! queue ! aacparse ! avdec_aac ! audioconvert ! alsasink
38 * ]| Parse and render TTML subtitles contained in a single XML file over an
39 * MP4 stream containing H.264 video and AAC audio:
40 *
41 */
42
43 #include <gst/video/video.h>
44 #include <gst/video/gstvideometa.h>
45 #include <gst/video/video-overlay-composition.h>
46 #include <pango/pangocairo.h>
47
48 #include <string.h>
49 #include <math.h>
50
51 #include "gstttmlelements.h"
52 #include "gstttmlrender.h"
53 #include "subtitle.h"
54 #include "subtitlemeta.h"
55
56 #define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
57
58 #define TTML_RENDER_CAPS GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS)
59
60 #define TTML_RENDER_ALL_CAPS TTML_RENDER_CAPS ";" \
61 GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
62
63 GST_DEBUG_CATEGORY_EXTERN (ttmlrender_debug);
64 #define GST_CAT_DEFAULT ttmlrender_debug
65
66 static GstStaticCaps sw_template_caps = GST_STATIC_CAPS (TTML_RENDER_CAPS);
67
68 static GstStaticPadTemplate src_template_factory =
69 GST_STATIC_PAD_TEMPLATE ("src",
70 GST_PAD_SRC,
71 GST_PAD_ALWAYS,
72 GST_STATIC_CAPS (TTML_RENDER_ALL_CAPS)
73 );
74
75 static GstStaticPadTemplate video_sink_template_factory =
76 GST_STATIC_PAD_TEMPLATE ("video_sink",
77 GST_PAD_SINK,
78 GST_PAD_ALWAYS,
79 GST_STATIC_CAPS (TTML_RENDER_ALL_CAPS)
80 );
81
82 static GstStaticPadTemplate text_sink_template_factory =
83 GST_STATIC_PAD_TEMPLATE ("text_sink",
84 GST_PAD_SINK,
85 GST_PAD_ALWAYS,
86 GST_STATIC_CAPS ("text/x-raw(meta:GstSubtitleMeta)")
87 );
88
89 #define GST_TTML_RENDER_GET_LOCK(ov) (&GST_TTML_RENDER (ov)->lock)
90 #define GST_TTML_RENDER_GET_COND(ov) (&GST_TTML_RENDER (ov)->cond)
91 #define GST_TTML_RENDER_LOCK(ov) (g_mutex_lock (GST_TTML_RENDER_GET_LOCK (ov)))
92 #define GST_TTML_RENDER_UNLOCK(ov) (g_mutex_unlock (GST_TTML_RENDER_GET_LOCK (ov)))
93 #define GST_TTML_RENDER_WAIT(ov) (g_cond_wait (GST_TTML_RENDER_GET_COND (ov), GST_TTML_RENDER_GET_LOCK (ov)))
94 #define GST_TTML_RENDER_SIGNAL(ov) (g_cond_signal (GST_TTML_RENDER_GET_COND (ov)))
95 #define GST_TTML_RENDER_BROADCAST(ov)(g_cond_broadcast (GST_TTML_RENDER_GET_COND (ov)))
96
97
98 typedef enum
99 {
100 GST_TTML_DIRECTION_INLINE,
101 GST_TTML_DIRECTION_BLOCK
102 } GstTtmlDirection;
103
104
105 typedef struct
106 {
107 guint line_height;
108 guint baseline_offset;
109 } BlockMetrics;
110
111
112 typedef struct
113 {
114 guint height;
115 guint baseline;
116 } FontMetrics;
117
118
119 typedef struct
120 {
121 guint first_index;
122 guint last_index;
123 } CharRange;
124
125
126 /* @pango_font_size is the font size you would need to tell pango in order that
127 * the actual rendered height of @text matches the text height in @element's
128 * style set. */
129 typedef struct
130 {
131 GstSubtitleElement *element;
132 guint pango_font_size;
133 FontMetrics pango_font_metrics;
134 gchar *text;
135 } UnifiedElement;
136
137
138 typedef struct
139 {
140 GPtrArray *unified_elements;
141 GstSubtitleStyleSet *style_set;
142 gchar *joined_text;
143 } UnifiedBlock;
144
145
146 static GstElementClass *parent_class = NULL;
147 static void gst_ttml_render_base_init (gpointer g_class);
148 static void gst_ttml_render_class_init (GstTtmlRenderClass * klass);
149 static void gst_ttml_render_init (GstTtmlRender * render,
150 GstTtmlRenderClass * klass);
151
152 static GstStateChangeReturn gst_ttml_render_change_state (GstElement *
153 element, GstStateChange transition);
154
155 static GstCaps *gst_ttml_render_get_videosink_caps (GstPad * pad,
156 GstTtmlRender * render, GstCaps * filter);
157 static GstCaps *gst_ttml_render_get_src_caps (GstPad * pad,
158 GstTtmlRender * render, GstCaps * filter);
159 static gboolean gst_ttml_render_setcaps (GstTtmlRender * render,
160 GstCaps * caps);
161 static gboolean gst_ttml_render_src_event (GstPad * pad,
162 GstObject * parent, GstEvent * event);
163 static gboolean gst_ttml_render_src_query (GstPad * pad,
164 GstObject * parent, GstQuery * query);
165
166 static gboolean gst_ttml_render_video_event (GstPad * pad,
167 GstObject * parent, GstEvent * event);
168 static gboolean gst_ttml_render_video_query (GstPad * pad,
169 GstObject * parent, GstQuery * query);
170 static GstFlowReturn gst_ttml_render_video_chain (GstPad * pad,
171 GstObject * parent, GstBuffer * buffer);
172
173 static gboolean gst_ttml_render_text_event (GstPad * pad,
174 GstObject * parent, GstEvent * event);
175 static GstFlowReturn gst_ttml_render_text_chain (GstPad * pad,
176 GstObject * parent, GstBuffer * buffer);
177 static GstPadLinkReturn gst_ttml_render_text_pad_link (GstPad * pad,
178 GstObject * parent, GstPad * peer);
179 static void gst_ttml_render_text_pad_unlink (GstPad * pad, GstObject * parent);
180 static void gst_ttml_render_pop_text (GstTtmlRender * render);
181
182 static void gst_ttml_render_finalize (GObject * object);
183
184 static gboolean gst_ttml_render_can_handle_caps (GstCaps * incaps);
185
186 static GstTtmlRenderRenderedImage *gst_ttml_render_rendered_image_new
187 (GstBuffer * image, gint x, gint y, guint width, guint height);
188 static GstTtmlRenderRenderedImage *gst_ttml_render_rendered_image_new_empty
189 (void);
190 static GstTtmlRenderRenderedImage *gst_ttml_render_rendered_image_copy
191 (GstTtmlRenderRenderedImage * image);
192 static void gst_ttml_render_rendered_image_free
193 (GstTtmlRenderRenderedImage * image);
194 static GstTtmlRenderRenderedImage *gst_ttml_render_rendered_image_combine
195 (GstTtmlRenderRenderedImage * image1, GstTtmlRenderRenderedImage * image2);
196 static GstTtmlRenderRenderedImage *gst_ttml_render_stitch_images (GPtrArray *
197 images, GstTtmlDirection direction);
198
199 static gboolean gst_ttml_render_color_is_transparent (GstSubtitleColor * color);
200 static gboolean gst_element_ttmlrender_init (GstPlugin * plugin);
201
202 GType
gst_ttml_render_get_type(void)203 gst_ttml_render_get_type (void)
204 {
205 static GType type = 0;
206
207 if (g_once_init_enter ((gsize *) & type)) {
208 static const GTypeInfo info = {
209 sizeof (GstTtmlRenderClass),
210 (GBaseInitFunc) gst_ttml_render_base_init,
211 NULL,
212 (GClassInitFunc) gst_ttml_render_class_init,
213 NULL,
214 NULL,
215 sizeof (GstTtmlRender),
216 0,
217 (GInstanceInitFunc) gst_ttml_render_init,
218 };
219
220 g_once_init_leave ((gsize *) & type,
221 g_type_register_static (GST_TYPE_ELEMENT, "GstTtmlRender", &info, 0));
222 }
223
224 return type;
225 }
226
227 GST_ELEMENT_REGISTER_DEFINE_CUSTOM (ttmlrender, gst_element_ttmlrender_init);
228
229 static void
gst_ttml_render_base_init(gpointer g_class)230 gst_ttml_render_base_init (gpointer g_class)
231 {
232 GstTtmlRenderClass *klass = GST_TTML_RENDER_CLASS (g_class);
233 PangoFontMap *fontmap;
234
235 /* Only lock for the subclasses here, the base class
236 * doesn't have this mutex yet and it's not necessary
237 * here */
238 if (klass->pango_lock)
239 g_mutex_lock (klass->pango_lock);
240 fontmap = pango_cairo_font_map_get_default ();
241 klass->pango_context =
242 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
243 if (klass->pango_lock)
244 g_mutex_unlock (klass->pango_lock);
245 }
246
247 static void
gst_ttml_render_class_init(GstTtmlRenderClass * klass)248 gst_ttml_render_class_init (GstTtmlRenderClass * klass)
249 {
250 GObjectClass *gobject_class;
251 GstElementClass *gstelement_class;
252
253 gobject_class = (GObjectClass *) klass;
254 gstelement_class = (GstElementClass *) klass;
255
256 parent_class = g_type_class_peek_parent (klass);
257
258 gobject_class->finalize = gst_ttml_render_finalize;
259
260 gst_element_class_add_pad_template (gstelement_class,
261 gst_static_pad_template_get (&src_template_factory));
262 gst_element_class_add_pad_template (gstelement_class,
263 gst_static_pad_template_get (&video_sink_template_factory));
264 gst_element_class_add_pad_template (gstelement_class,
265 gst_static_pad_template_get (&text_sink_template_factory));
266
267 gst_element_class_set_static_metadata (gstelement_class,
268 "TTML subtitle renderer", "Overlay/Subtitle",
269 "Renders timed-text subtitles on top of video buffers",
270 "David Schleef <ds@schleef.org>, Zeeshan Ali <zeeshan.ali@nokia.com>, "
271 "Chris Bass <dash@rd.bbc.co.uk>");
272
273 gstelement_class->change_state =
274 GST_DEBUG_FUNCPTR (gst_ttml_render_change_state);
275
276 klass->pango_lock = g_slice_new (GMutex);
277 g_mutex_init (klass->pango_lock);
278 }
279
280 static void
gst_ttml_render_finalize(GObject * object)281 gst_ttml_render_finalize (GObject * object)
282 {
283 GstTtmlRender *render = GST_TTML_RENDER (object);
284
285 if (render->compositions) {
286 g_list_free_full (render->compositions,
287 (GDestroyNotify) gst_video_overlay_composition_unref);
288 render->compositions = NULL;
289 }
290
291 if (render->text_buffer) {
292 gst_buffer_unref (render->text_buffer);
293 render->text_buffer = NULL;
294 }
295
296 if (render->layout) {
297 g_object_unref (render->layout);
298 render->layout = NULL;
299 }
300
301 g_mutex_clear (&render->lock);
302 g_cond_clear (&render->cond);
303
304 G_OBJECT_CLASS (parent_class)->finalize (object);
305 }
306
307 static void
gst_ttml_render_init(GstTtmlRender * render,GstTtmlRenderClass * klass)308 gst_ttml_render_init (GstTtmlRender * render, GstTtmlRenderClass * klass)
309 {
310 GstPadTemplate *template;
311
312 /* video sink */
313 template = gst_static_pad_template_get (&video_sink_template_factory);
314 render->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
315 gst_object_unref (template);
316 gst_pad_set_event_function (render->video_sinkpad,
317 GST_DEBUG_FUNCPTR (gst_ttml_render_video_event));
318 gst_pad_set_chain_function (render->video_sinkpad,
319 GST_DEBUG_FUNCPTR (gst_ttml_render_video_chain));
320 gst_pad_set_query_function (render->video_sinkpad,
321 GST_DEBUG_FUNCPTR (gst_ttml_render_video_query));
322 GST_PAD_SET_PROXY_ALLOCATION (render->video_sinkpad);
323 gst_element_add_pad (GST_ELEMENT (render), render->video_sinkpad);
324
325 template =
326 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
327 "text_sink");
328 if (template) {
329 /* text sink */
330 render->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
331
332 gst_pad_set_event_function (render->text_sinkpad,
333 GST_DEBUG_FUNCPTR (gst_ttml_render_text_event));
334 gst_pad_set_chain_function (render->text_sinkpad,
335 GST_DEBUG_FUNCPTR (gst_ttml_render_text_chain));
336 gst_pad_set_link_function (render->text_sinkpad,
337 GST_DEBUG_FUNCPTR (gst_ttml_render_text_pad_link));
338 gst_pad_set_unlink_function (render->text_sinkpad,
339 GST_DEBUG_FUNCPTR (gst_ttml_render_text_pad_unlink));
340 gst_element_add_pad (GST_ELEMENT (render), render->text_sinkpad);
341 }
342
343 /* (video) source */
344 template = gst_static_pad_template_get (&src_template_factory);
345 render->srcpad = gst_pad_new_from_template (template, "src");
346 gst_object_unref (template);
347 gst_pad_set_event_function (render->srcpad,
348 GST_DEBUG_FUNCPTR (gst_ttml_render_src_event));
349 gst_pad_set_query_function (render->srcpad,
350 GST_DEBUG_FUNCPTR (gst_ttml_render_src_query));
351 gst_element_add_pad (GST_ELEMENT (render), render->srcpad);
352
353 g_mutex_lock (GST_TTML_RENDER_GET_CLASS (render)->pango_lock);
354
355 render->wait_text = TRUE;
356 render->need_render = TRUE;
357 render->text_buffer = NULL;
358 render->text_linked = FALSE;
359
360 render->compositions = NULL;
361 render->layout =
362 pango_layout_new (GST_TTML_RENDER_GET_CLASS (render)->pango_context);
363
364 g_mutex_init (&render->lock);
365 g_cond_init (&render->cond);
366 gst_segment_init (&render->segment, GST_FORMAT_TIME);
367 g_mutex_unlock (GST_TTML_RENDER_GET_CLASS (render)->pango_lock);
368 }
369
370
371 /* only negotiate/query video render composition support for now */
372 static gboolean
gst_ttml_render_negotiate(GstTtmlRender * render,GstCaps * caps)373 gst_ttml_render_negotiate (GstTtmlRender * render, GstCaps * caps)
374 {
375 GstQuery *query;
376 gboolean attach = FALSE;
377 gboolean caps_has_meta = TRUE;
378 gboolean ret;
379 GstCapsFeatures *f;
380 GstCaps *original_caps;
381 gboolean original_has_meta = FALSE;
382 gboolean allocation_ret = TRUE;
383
384 GST_DEBUG_OBJECT (render, "performing negotiation");
385
386 gst_pad_check_reconfigure (render->srcpad);
387
388 if (!caps)
389 caps = gst_pad_get_current_caps (render->video_sinkpad);
390 else
391 gst_caps_ref (caps);
392
393 if (!caps || gst_caps_is_empty (caps))
394 goto no_format;
395
396 original_caps = caps;
397
398 /* Try to use the render meta if possible */
399 f = gst_caps_get_features (caps, 0);
400
401 /* if the caps doesn't have the render meta, we query if downstream
402 * accepts it before trying the version without the meta
403 * If upstream already is using the meta then we can only use it */
404 if (!f
405 || !gst_caps_features_contains (f,
406 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION)) {
407 GstCaps *overlay_caps;
408
409 /* In this case we added the meta, but we can work without it
410 * so preserve the original caps so we can use it as a fallback */
411 overlay_caps = gst_caps_copy (caps);
412
413 f = gst_caps_get_features (overlay_caps, 0);
414 gst_caps_features_add (f,
415 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
416
417 ret = gst_pad_peer_query_accept_caps (render->srcpad, overlay_caps);
418 GST_DEBUG_OBJECT (render, "Downstream accepts the render meta: %d", ret);
419 if (ret) {
420 gst_caps_unref (caps);
421 caps = overlay_caps;
422
423 } else {
424 /* fallback to the original */
425 gst_caps_unref (overlay_caps);
426 caps_has_meta = FALSE;
427 }
428 } else {
429 original_has_meta = TRUE;
430 }
431 GST_DEBUG_OBJECT (render, "Using caps %" GST_PTR_FORMAT, caps);
432 ret = gst_pad_set_caps (render->srcpad, caps);
433
434 if (ret) {
435 /* find supported meta */
436 query = gst_query_new_allocation (caps, FALSE);
437
438 if (!gst_pad_peer_query (render->srcpad, query)) {
439 /* no problem, we use the query defaults */
440 GST_DEBUG_OBJECT (render, "ALLOCATION query failed");
441 allocation_ret = FALSE;
442 }
443
444 if (caps_has_meta && gst_query_find_allocation_meta (query,
445 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
446 attach = TRUE;
447
448 gst_query_unref (query);
449 }
450
451 if (!allocation_ret && render->video_flushing) {
452 ret = FALSE;
453 } else if (original_caps && !original_has_meta && !attach) {
454 if (caps_has_meta) {
455 /* Some elements (fakesink) claim to accept the meta on caps but won't
456 put it in the allocation query result, this leads below
457 check to fail. Prevent this by removing the meta from caps */
458 gst_caps_unref (caps);
459 caps = gst_caps_ref (original_caps);
460 ret = gst_pad_set_caps (render->srcpad, caps);
461 if (ret && !gst_ttml_render_can_handle_caps (caps))
462 ret = FALSE;
463 }
464 }
465
466 if (!ret) {
467 GST_DEBUG_OBJECT (render, "negotiation failed, schedule reconfigure");
468 gst_pad_mark_reconfigure (render->srcpad);
469 }
470
471 gst_caps_unref (caps);
472
473 if (!ret)
474 gst_pad_mark_reconfigure (render->srcpad);
475
476 return ret;
477
478 no_format:
479 {
480 if (caps)
481 gst_caps_unref (caps);
482 gst_pad_mark_reconfigure (render->srcpad);
483 return FALSE;
484 }
485 }
486
487 static gboolean
gst_ttml_render_can_handle_caps(GstCaps * incaps)488 gst_ttml_render_can_handle_caps (GstCaps * incaps)
489 {
490 gboolean ret;
491 GstCaps *caps;
492 static GstStaticCaps static_caps = GST_STATIC_CAPS (TTML_RENDER_CAPS);
493
494 caps = gst_static_caps_get (&static_caps);
495 ret = gst_caps_is_subset (incaps, caps);
496 gst_caps_unref (caps);
497
498 return ret;
499 }
500
501 static gboolean
gst_ttml_render_setcaps(GstTtmlRender * render,GstCaps * caps)502 gst_ttml_render_setcaps (GstTtmlRender * render, GstCaps * caps)
503 {
504 GstVideoInfo info;
505 gboolean ret = FALSE;
506
507 if (!gst_video_info_from_caps (&info, caps))
508 goto invalid_caps;
509
510 render->info = info;
511 render->format = GST_VIDEO_INFO_FORMAT (&info);
512 render->width = GST_VIDEO_INFO_WIDTH (&info);
513 render->height = GST_VIDEO_INFO_HEIGHT (&info);
514
515 ret = gst_ttml_render_negotiate (render, caps);
516
517 GST_TTML_RENDER_LOCK (render);
518 g_mutex_lock (GST_TTML_RENDER_GET_CLASS (render)->pango_lock);
519 if (!gst_ttml_render_can_handle_caps (caps)) {
520 GST_DEBUG_OBJECT (render, "unsupported caps %" GST_PTR_FORMAT, caps);
521 ret = FALSE;
522 }
523
524 g_mutex_unlock (GST_TTML_RENDER_GET_CLASS (render)->pango_lock);
525 GST_TTML_RENDER_UNLOCK (render);
526
527 return ret;
528
529 /* ERRORS */
530 invalid_caps:
531 {
532 GST_DEBUG_OBJECT (render, "could not parse caps");
533 return FALSE;
534 }
535 }
536
537
538 static gboolean
gst_ttml_render_src_query(GstPad * pad,GstObject * parent,GstQuery * query)539 gst_ttml_render_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
540 {
541 gboolean ret = FALSE;
542 GstTtmlRender *render;
543
544 render = GST_TTML_RENDER (parent);
545
546 switch (GST_QUERY_TYPE (query)) {
547 case GST_QUERY_CAPS:
548 {
549 GstCaps *filter, *caps;
550
551 gst_query_parse_caps (query, &filter);
552 caps = gst_ttml_render_get_src_caps (pad, render, filter);
553 gst_query_set_caps_result (query, caps);
554 gst_caps_unref (caps);
555 ret = TRUE;
556 break;
557 }
558 default:
559 ret = gst_pad_query_default (pad, parent, query);
560 break;
561 }
562
563 return ret;
564 }
565
566 static gboolean
gst_ttml_render_src_event(GstPad * pad,GstObject * parent,GstEvent * event)567 gst_ttml_render_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
568 {
569 GstTtmlRender *render;
570 gboolean ret;
571
572 render = GST_TTML_RENDER (parent);
573
574 if (render->text_linked) {
575 gst_event_ref (event);
576 ret = gst_pad_push_event (render->video_sinkpad, event);
577 gst_pad_push_event (render->text_sinkpad, event);
578 } else {
579 ret = gst_pad_push_event (render->video_sinkpad, event);
580 }
581
582 return ret;
583 }
584
585 /**
586 * gst_ttml_render_add_feature_and_intersect:
587 *
588 * Creates a new #GstCaps containing the (given caps +
589 * given caps feature) + (given caps intersected by the
590 * given filter).
591 *
592 * Returns: the new #GstCaps
593 */
594 static GstCaps *
gst_ttml_render_add_feature_and_intersect(GstCaps * caps,const gchar * feature,GstCaps * filter)595 gst_ttml_render_add_feature_and_intersect (GstCaps * caps,
596 const gchar * feature, GstCaps * filter)
597 {
598 int i, caps_size;
599 GstCaps *new_caps;
600
601 new_caps = gst_caps_copy (caps);
602
603 caps_size = gst_caps_get_size (new_caps);
604 for (i = 0; i < caps_size; i++) {
605 GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
606
607 if (!gst_caps_features_is_any (features)) {
608 gst_caps_features_add (features, feature);
609 }
610 }
611
612 gst_caps_append (new_caps, gst_caps_intersect_full (caps,
613 filter, GST_CAPS_INTERSECT_FIRST));
614
615 return new_caps;
616 }
617
618 /**
619 * gst_ttml_render_intersect_by_feature:
620 *
621 * Creates a new #GstCaps based on the following filtering rule.
622 *
623 * For each individual caps contained in given caps, if the
624 * caps uses the given caps feature, keep a version of the caps
625 * with the feature and an another one without. Otherwise, intersect
626 * the caps with the given filter.
627 *
628 * Returns: the new #GstCaps
629 */
630 static GstCaps *
gst_ttml_render_intersect_by_feature(GstCaps * caps,const gchar * feature,GstCaps * filter)631 gst_ttml_render_intersect_by_feature (GstCaps * caps,
632 const gchar * feature, GstCaps * filter)
633 {
634 int i, caps_size;
635 GstCaps *new_caps;
636
637 new_caps = gst_caps_new_empty ();
638
639 caps_size = gst_caps_get_size (caps);
640 for (i = 0; i < caps_size; i++) {
641 GstStructure *caps_structure = gst_caps_get_structure (caps, i);
642 GstCapsFeatures *caps_features =
643 gst_caps_features_copy (gst_caps_get_features (caps, i));
644 GstCaps *filtered_caps;
645 GstCaps *simple_caps =
646 gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
647 gst_caps_set_features (simple_caps, 0, caps_features);
648
649 if (gst_caps_features_contains (caps_features, feature)) {
650 gst_caps_append (new_caps, gst_caps_copy (simple_caps));
651
652 gst_caps_features_remove (caps_features, feature);
653 filtered_caps = gst_caps_ref (simple_caps);
654 } else {
655 filtered_caps = gst_caps_intersect_full (simple_caps, filter,
656 GST_CAPS_INTERSECT_FIRST);
657 }
658
659 gst_caps_unref (simple_caps);
660 gst_caps_append (new_caps, filtered_caps);
661 }
662
663 return new_caps;
664 }
665
666 static GstCaps *
gst_ttml_render_get_videosink_caps(GstPad * pad,GstTtmlRender * render,GstCaps * filter)667 gst_ttml_render_get_videosink_caps (GstPad * pad,
668 GstTtmlRender * render, GstCaps * filter)
669 {
670 GstPad *srcpad = render->srcpad;
671 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
672
673 if (G_UNLIKELY (!render))
674 return gst_pad_get_pad_template_caps (pad);
675
676 if (filter) {
677 /* filter caps + composition feature + filter caps
678 * filtered by the software caps. */
679 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
680 overlay_filter = gst_ttml_render_add_feature_and_intersect (filter,
681 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
682 gst_caps_unref (sw_caps);
683
684 GST_DEBUG_OBJECT (render, "render filter %" GST_PTR_FORMAT, overlay_filter);
685 }
686
687 peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);
688
689 if (overlay_filter)
690 gst_caps_unref (overlay_filter);
691
692 if (peer_caps) {
693
694 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
695
696 if (gst_caps_is_any (peer_caps)) {
697 /* if peer returns ANY caps, return filtered src pad template caps */
698 caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
699 } else {
700
701 /* duplicate caps which contains the composition into one version with
702 * the meta and one without. Filter the other caps by the software caps */
703 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
704 caps = gst_ttml_render_intersect_by_feature (peer_caps,
705 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
706 gst_caps_unref (sw_caps);
707 }
708
709 gst_caps_unref (peer_caps);
710
711 } else {
712 /* no peer, our padtemplate is enough then */
713 caps = gst_pad_get_pad_template_caps (pad);
714 }
715
716 if (filter) {
717 GstCaps *intersection = gst_caps_intersect_full (filter, caps,
718 GST_CAPS_INTERSECT_FIRST);
719 gst_caps_unref (caps);
720 caps = intersection;
721 }
722
723 GST_DEBUG_OBJECT (render, "returning %" GST_PTR_FORMAT, caps);
724
725 return caps;
726 }
727
728 static GstCaps *
gst_ttml_render_get_src_caps(GstPad * pad,GstTtmlRender * render,GstCaps * filter)729 gst_ttml_render_get_src_caps (GstPad * pad, GstTtmlRender * render,
730 GstCaps * filter)
731 {
732 GstPad *sinkpad = render->video_sinkpad;
733 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
734
735 if (G_UNLIKELY (!render))
736 return gst_pad_get_pad_template_caps (pad);
737
738 if (filter) {
739 /* duplicate filter caps which contains the composition into one version
740 * with the meta and one without. Filter the other caps by the software
741 * caps */
742 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
743 overlay_filter =
744 gst_ttml_render_intersect_by_feature (filter,
745 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
746 gst_caps_unref (sw_caps);
747 }
748
749 peer_caps = gst_pad_peer_query_caps (sinkpad, overlay_filter);
750
751 if (overlay_filter)
752 gst_caps_unref (overlay_filter);
753
754 if (peer_caps) {
755
756 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
757
758 if (gst_caps_is_any (peer_caps)) {
759
760 /* if peer returns ANY caps, return filtered sink pad template caps */
761 caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad));
762
763 } else {
764
765 /* return upstream caps + composition feature + upstream caps
766 * filtered by the software caps. */
767 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
768 caps = gst_ttml_render_add_feature_and_intersect (peer_caps,
769 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
770 gst_caps_unref (sw_caps);
771 }
772
773 gst_caps_unref (peer_caps);
774
775 } else {
776 /* no peer, our padtemplate is enough then */
777 caps = gst_pad_get_pad_template_caps (pad);
778 }
779
780 if (filter) {
781 GstCaps *intersection;
782
783 intersection =
784 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
785 gst_caps_unref (caps);
786 caps = intersection;
787 }
788 GST_DEBUG_OBJECT (render, "returning %" GST_PTR_FORMAT, caps);
789
790 return caps;
791 }
792
793
794 static GstFlowReturn
gst_ttml_render_push_frame(GstTtmlRender * render,GstBuffer * video_frame)795 gst_ttml_render_push_frame (GstTtmlRender * render, GstBuffer * video_frame)
796 {
797 GstVideoFrame frame;
798 GList *compositions = render->compositions;
799
800 if (compositions == NULL) {
801 GST_CAT_DEBUG (ttmlrender_debug, "No compositions.");
802 goto done;
803 }
804
805 if (gst_pad_check_reconfigure (render->srcpad)) {
806 if (!gst_ttml_render_negotiate (render, NULL)) {
807 gst_pad_mark_reconfigure (render->srcpad);
808 gst_buffer_unref (video_frame);
809 if (GST_PAD_IS_FLUSHING (render->srcpad))
810 return GST_FLOW_FLUSHING;
811 else
812 return GST_FLOW_NOT_NEGOTIATED;
813 }
814 }
815
816 video_frame = gst_buffer_make_writable (video_frame);
817
818 if (!gst_video_frame_map (&frame, &render->info, video_frame,
819 GST_MAP_READWRITE))
820 goto invalid_frame;
821
822 while (compositions) {
823 GstVideoOverlayComposition *composition = compositions->data;
824 gst_video_overlay_composition_blend (composition, &frame);
825 compositions = compositions->next;
826 }
827
828 gst_video_frame_unmap (&frame);
829
830 done:
831
832 return gst_pad_push (render->srcpad, video_frame);
833
834 /* ERRORS */
835 invalid_frame:
836 {
837 gst_buffer_unref (video_frame);
838 GST_DEBUG_OBJECT (render, "received invalid buffer");
839 return GST_FLOW_OK;
840 }
841 }
842
843 static GstPadLinkReturn
gst_ttml_render_text_pad_link(GstPad * pad,GstObject * parent,GstPad * peer)844 gst_ttml_render_text_pad_link (GstPad * pad, GstObject * parent, GstPad * peer)
845 {
846 GstTtmlRender *render;
847
848 render = GST_TTML_RENDER (parent);
849 if (G_UNLIKELY (!render))
850 return GST_PAD_LINK_REFUSED;
851
852 GST_DEBUG_OBJECT (render, "Text pad linked");
853
854 render->text_linked = TRUE;
855
856 return GST_PAD_LINK_OK;
857 }
858
859 static void
gst_ttml_render_text_pad_unlink(GstPad * pad,GstObject * parent)860 gst_ttml_render_text_pad_unlink (GstPad * pad, GstObject * parent)
861 {
862 GstTtmlRender *render;
863
864 /* don't use gst_pad_get_parent() here, will deadlock */
865 render = GST_TTML_RENDER (parent);
866
867 GST_DEBUG_OBJECT (render, "Text pad unlinked");
868
869 render->text_linked = FALSE;
870
871 gst_segment_init (&render->text_segment, GST_FORMAT_UNDEFINED);
872 }
873
874 static gboolean
gst_ttml_render_text_event(GstPad * pad,GstObject * parent,GstEvent * event)875 gst_ttml_render_text_event (GstPad * pad, GstObject * parent, GstEvent * event)
876 {
877 gboolean ret = FALSE;
878 GstTtmlRender *render = NULL;
879
880 render = GST_TTML_RENDER (parent);
881
882 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
883
884 switch (GST_EVENT_TYPE (event)) {
885 case GST_EVENT_SEGMENT:
886 {
887 const GstSegment *segment;
888
889 render->text_eos = FALSE;
890
891 gst_event_parse_segment (event, &segment);
892
893 if (segment->format == GST_FORMAT_TIME) {
894 GST_TTML_RENDER_LOCK (render);
895 gst_segment_copy_into (segment, &render->text_segment);
896 GST_DEBUG_OBJECT (render, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
897 &render->text_segment);
898 GST_TTML_RENDER_UNLOCK (render);
899 } else {
900 GST_ELEMENT_WARNING (render, STREAM, MUX, (NULL),
901 ("received non-TIME newsegment event on text input"));
902 }
903
904 gst_event_unref (event);
905 ret = TRUE;
906
907 /* wake up the video chain, it might be waiting for a text buffer or
908 * a text segment update */
909 GST_TTML_RENDER_LOCK (render);
910 GST_TTML_RENDER_BROADCAST (render);
911 GST_TTML_RENDER_UNLOCK (render);
912 break;
913 }
914 case GST_EVENT_GAP:
915 {
916 GstClockTime start, duration;
917
918 gst_event_parse_gap (event, &start, &duration);
919 if (GST_CLOCK_TIME_IS_VALID (duration))
920 start += duration;
921 /* we do not expect another buffer until after gap,
922 * so that is our position now */
923 render->text_segment.position = start;
924
925 /* wake up the video chain, it might be waiting for a text buffer or
926 * a text segment update */
927 GST_TTML_RENDER_LOCK (render);
928 GST_TTML_RENDER_BROADCAST (render);
929 GST_TTML_RENDER_UNLOCK (render);
930
931 gst_event_unref (event);
932 ret = TRUE;
933 break;
934 }
935 case GST_EVENT_FLUSH_STOP:
936 GST_TTML_RENDER_LOCK (render);
937 GST_INFO_OBJECT (render, "text flush stop");
938 render->text_flushing = FALSE;
939 render->text_eos = FALSE;
940 gst_ttml_render_pop_text (render);
941 gst_segment_init (&render->text_segment, GST_FORMAT_TIME);
942 GST_TTML_RENDER_UNLOCK (render);
943 gst_event_unref (event);
944 ret = TRUE;
945 break;
946 case GST_EVENT_FLUSH_START:
947 GST_TTML_RENDER_LOCK (render);
948 GST_INFO_OBJECT (render, "text flush start");
949 render->text_flushing = TRUE;
950 GST_TTML_RENDER_BROADCAST (render);
951 GST_TTML_RENDER_UNLOCK (render);
952 gst_event_unref (event);
953 ret = TRUE;
954 break;
955 case GST_EVENT_EOS:
956 GST_TTML_RENDER_LOCK (render);
957 render->text_eos = TRUE;
958 GST_INFO_OBJECT (render, "text EOS");
959 /* wake up the video chain, it might be waiting for a text buffer or
960 * a text segment update */
961 GST_TTML_RENDER_BROADCAST (render);
962 GST_TTML_RENDER_UNLOCK (render);
963 gst_event_unref (event);
964 ret = TRUE;
965 break;
966 default:
967 ret = gst_pad_event_default (pad, parent, event);
968 break;
969 }
970
971 return ret;
972 }
973
974 static gboolean
gst_ttml_render_video_event(GstPad * pad,GstObject * parent,GstEvent * event)975 gst_ttml_render_video_event (GstPad * pad, GstObject * parent, GstEvent * event)
976 {
977 gboolean ret = FALSE;
978 GstTtmlRender *render = NULL;
979
980 render = GST_TTML_RENDER (parent);
981
982 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
983
984 switch (GST_EVENT_TYPE (event)) {
985 case GST_EVENT_CAPS:
986 {
987 GstCaps *caps;
988 gint prev_width = render->width;
989 gint prev_height = render->height;
990
991 gst_event_parse_caps (event, &caps);
992 ret = gst_ttml_render_setcaps (render, caps);
993 if (render->width != prev_width || render->height != prev_height)
994 render->need_render = TRUE;
995 gst_event_unref (event);
996 break;
997 }
998 case GST_EVENT_SEGMENT:
999 {
1000 const GstSegment *segment;
1001
1002 GST_DEBUG_OBJECT (render, "received new segment");
1003
1004 gst_event_parse_segment (event, &segment);
1005
1006 if (segment->format == GST_FORMAT_TIME) {
1007 GST_DEBUG_OBJECT (render, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1008 &render->segment);
1009
1010 gst_segment_copy_into (segment, &render->segment);
1011 } else {
1012 GST_ELEMENT_WARNING (render, STREAM, MUX, (NULL),
1013 ("received non-TIME newsegment event on video input"));
1014 }
1015
1016 ret = gst_pad_event_default (pad, parent, event);
1017 break;
1018 }
1019 case GST_EVENT_EOS:
1020 GST_TTML_RENDER_LOCK (render);
1021 GST_INFO_OBJECT (render, "video EOS");
1022 render->video_eos = TRUE;
1023 GST_TTML_RENDER_UNLOCK (render);
1024 ret = gst_pad_event_default (pad, parent, event);
1025 break;
1026 case GST_EVENT_FLUSH_START:
1027 GST_TTML_RENDER_LOCK (render);
1028 GST_INFO_OBJECT (render, "video flush start");
1029 render->video_flushing = TRUE;
1030 GST_TTML_RENDER_BROADCAST (render);
1031 GST_TTML_RENDER_UNLOCK (render);
1032 ret = gst_pad_event_default (pad, parent, event);
1033 break;
1034 case GST_EVENT_FLUSH_STOP:
1035 GST_TTML_RENDER_LOCK (render);
1036 GST_INFO_OBJECT (render, "video flush stop");
1037 render->video_flushing = FALSE;
1038 render->video_eos = FALSE;
1039 gst_segment_init (&render->segment, GST_FORMAT_TIME);
1040 GST_TTML_RENDER_UNLOCK (render);
1041 ret = gst_pad_event_default (pad, parent, event);
1042 break;
1043 default:
1044 ret = gst_pad_event_default (pad, parent, event);
1045 break;
1046 }
1047
1048 return ret;
1049 }
1050
1051 static gboolean
gst_ttml_render_video_query(GstPad * pad,GstObject * parent,GstQuery * query)1052 gst_ttml_render_video_query (GstPad * pad, GstObject * parent, GstQuery * query)
1053 {
1054 gboolean ret = FALSE;
1055 GstTtmlRender *render;
1056
1057 render = GST_TTML_RENDER (parent);
1058
1059 switch (GST_QUERY_TYPE (query)) {
1060 case GST_QUERY_CAPS:
1061 {
1062 GstCaps *filter, *caps;
1063
1064 gst_query_parse_caps (query, &filter);
1065 caps = gst_ttml_render_get_videosink_caps (pad, render, filter);
1066 gst_query_set_caps_result (query, caps);
1067 gst_caps_unref (caps);
1068 ret = TRUE;
1069 break;
1070 }
1071 default:
1072 ret = gst_pad_query_default (pad, parent, query);
1073 break;
1074 }
1075
1076 return ret;
1077 }
1078
1079 /* Called with lock held */
1080 static void
gst_ttml_render_pop_text(GstTtmlRender * render)1081 gst_ttml_render_pop_text (GstTtmlRender * render)
1082 {
1083 g_return_if_fail (GST_IS_TTML_RENDER (render));
1084
1085 if (render->text_buffer) {
1086 GST_DEBUG_OBJECT (render, "releasing text buffer %p", render->text_buffer);
1087 gst_buffer_unref (render->text_buffer);
1088 render->text_buffer = NULL;
1089 }
1090
1091 /* Let the text task know we used that buffer */
1092 GST_TTML_RENDER_BROADCAST (render);
1093 }
1094
1095 /* We receive text buffers here. If they are out of segment we just ignore them.
1096 If the buffer is in our segment we keep it internally except if another one
1097 is already waiting here, in that case we wait that it gets kicked out */
1098 static GstFlowReturn
gst_ttml_render_text_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)1099 gst_ttml_render_text_chain (GstPad * pad, GstObject * parent,
1100 GstBuffer * buffer)
1101 {
1102 GstFlowReturn ret = GST_FLOW_OK;
1103 GstTtmlRender *render = NULL;
1104 gboolean in_seg = FALSE;
1105 guint64 clip_start = 0, clip_stop = 0;
1106
1107 render = GST_TTML_RENDER (parent);
1108
1109 GST_TTML_RENDER_LOCK (render);
1110
1111 if (render->text_flushing) {
1112 GST_TTML_RENDER_UNLOCK (render);
1113 ret = GST_FLOW_FLUSHING;
1114 GST_LOG_OBJECT (render, "text flushing");
1115 goto beach;
1116 }
1117
1118 if (render->text_eos) {
1119 GST_TTML_RENDER_UNLOCK (render);
1120 ret = GST_FLOW_EOS;
1121 GST_LOG_OBJECT (render, "text EOS");
1122 goto beach;
1123 }
1124
1125 GST_LOG_OBJECT (render, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1126 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &render->segment,
1127 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1128 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1129 GST_BUFFER_DURATION (buffer)));
1130
1131 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1132 GstClockTime stop;
1133
1134 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1135 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1136 else
1137 stop = GST_CLOCK_TIME_NONE;
1138
1139 in_seg = gst_segment_clip (&render->text_segment, GST_FORMAT_TIME,
1140 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1141 } else {
1142 in_seg = TRUE;
1143 }
1144
1145 if (in_seg) {
1146 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1147 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1148 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
1149 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1150
1151 /* Wait for the previous buffer to go away */
1152 while (render->text_buffer != NULL) {
1153 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
1154 GST_DEBUG_PAD_NAME (pad));
1155 GST_TTML_RENDER_WAIT (render);
1156 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
1157 if (render->text_flushing) {
1158 GST_TTML_RENDER_UNLOCK (render);
1159 ret = GST_FLOW_FLUSHING;
1160 goto beach;
1161 }
1162 }
1163
1164 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1165 render->text_segment.position = clip_start;
1166
1167 render->text_buffer = buffer;
1168 /* That's a new text buffer we need to render */
1169 render->need_render = TRUE;
1170
1171 /* in case the video chain is waiting for a text buffer, wake it up */
1172 GST_TTML_RENDER_BROADCAST (render);
1173 }
1174
1175 GST_TTML_RENDER_UNLOCK (render);
1176
1177 beach:
1178
1179 return ret;
1180 }
1181
1182
1183 /* Caller needs to free returned string after use. */
1184 static gchar *
gst_ttml_render_color_to_string(GstSubtitleColor color)1185 gst_ttml_render_color_to_string (GstSubtitleColor color)
1186 {
1187 #if PANGO_VERSION_CHECK (1,38,0)
1188 return g_strdup_printf ("#%02x%02x%02x%02x",
1189 color.r, color.g, color.b, color.a);
1190 #else
1191 return g_strdup_printf ("#%02x%02x%02x", color.r, color.g, color.b);
1192 #endif
1193 }
1194
1195
1196 static GstBuffer *
gst_ttml_render_draw_rectangle(guint width,guint height,GstSubtitleColor color)1197 gst_ttml_render_draw_rectangle (guint width, guint height,
1198 GstSubtitleColor color)
1199 {
1200 GstMapInfo map;
1201 cairo_surface_t *surface;
1202 cairo_t *cairo_state;
1203 GstBuffer *buffer = gst_buffer_new_allocate (NULL, 4 * width * height, NULL);
1204
1205 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1206 surface = cairo_image_surface_create_for_data (map.data,
1207 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1208 cairo_state = cairo_create (surface);
1209
1210 /* clear surface */
1211 cairo_set_operator (cairo_state, CAIRO_OPERATOR_CLEAR);
1212 cairo_paint (cairo_state);
1213 cairo_set_operator (cairo_state, CAIRO_OPERATOR_OVER);
1214
1215 cairo_save (cairo_state);
1216 cairo_set_source_rgba (cairo_state, color.r / 255.0, color.g / 255.0,
1217 color.b / 255.0, color.a / 255.0);
1218 cairo_paint (cairo_state);
1219 cairo_restore (cairo_state);
1220 cairo_destroy (cairo_state);
1221 cairo_surface_destroy (surface);
1222 gst_buffer_unmap (buffer, &map);
1223
1224 return buffer;
1225 }
1226
1227
1228 static void
gst_ttml_render_char_range_free(CharRange * range)1229 gst_ttml_render_char_range_free (CharRange * range)
1230 {
1231 g_slice_free (CharRange, range);
1232 }
1233
1234
1235 /* Choose fonts for generic fontnames based upon IMSC1 and HbbTV specs. */
1236 static gchar *
gst_ttml_render_resolve_generic_fontname(const gchar * name)1237 gst_ttml_render_resolve_generic_fontname (const gchar * name)
1238 {
1239 if ((g_strcmp0 (name, "default") == 0)) {
1240 return
1241 g_strdup ("TiresiasScreenfont,Liberation Mono,Courier New,monospace");
1242 } else if ((g_strcmp0 (name, "monospace") == 0)) {
1243 return g_strdup ("Letter Gothic,Liberation Mono,Courier New,monospace");
1244 } else if ((g_strcmp0 (name, "sansSerif") == 0)) {
1245 return g_strdup ("TiresiasScreenfont,sans");
1246 } else if ((g_strcmp0 (name, "serif") == 0)) {
1247 return g_strdup ("serif");
1248 } else if ((g_strcmp0 (name, "monospaceSansSerif") == 0)) {
1249 return g_strdup ("Letter Gothic,monospace");
1250 } else if ((g_strcmp0 (name, "monospaceSerif") == 0)) {
1251 return g_strdup ("Courier New,Liberation Mono,monospace");
1252 } else if ((g_strcmp0 (name, "proportionalSansSerif") == 0)) {
1253 return g_strdup ("TiresiasScreenfont,Arial,Helvetica,Liberation Sans,sans");
1254 } else if ((g_strcmp0 (name, "proportionalSerif") == 0)) {
1255 return g_strdup ("serif");
1256 } else {
1257 return NULL;
1258 }
1259 }
1260
1261
1262 static gchar *
gst_ttml_render_get_text_from_buffer(GstBuffer * buf,guint index)1263 gst_ttml_render_get_text_from_buffer (GstBuffer * buf, guint index)
1264 {
1265 GstMapInfo map;
1266 GstMemory *mem;
1267 gchar *buf_text = NULL;
1268
1269 mem = gst_buffer_get_memory (buf, index);
1270 if (!mem) {
1271 GST_CAT_ERROR (ttmlrender_debug, "Failed to access memory at index %u.",
1272 index);
1273 return NULL;
1274 }
1275
1276 if (!gst_memory_map (mem, &map, GST_MAP_READ)) {
1277 GST_CAT_ERROR (ttmlrender_debug, "Failed to map memory at index %u.",
1278 index);
1279 goto map_fail;
1280 }
1281
1282 buf_text = g_strndup ((const gchar *) map.data, map.size);
1283 if (!g_utf8_validate (buf_text, -1, NULL)) {
1284 GST_CAT_ERROR (ttmlrender_debug, "Text in buffer us not valid UTF-8");
1285 g_free (buf_text);
1286 buf_text = NULL;
1287 }
1288
1289 gst_memory_unmap (mem, &map);
1290 map_fail:
1291 gst_memory_unref (mem);
1292 return buf_text;
1293 }
1294
1295
1296 static void
gst_ttml_render_unified_element_free(UnifiedElement * unified_element)1297 gst_ttml_render_unified_element_free (UnifiedElement * unified_element)
1298 {
1299 if (!unified_element)
1300 return;
1301
1302 gst_subtitle_element_unref (unified_element->element);
1303 g_free (unified_element->text);
1304 g_slice_free (UnifiedElement, unified_element);
1305 }
1306
1307
1308 static UnifiedElement *
gst_ttml_render_unified_element_copy(const UnifiedElement * unified_element)1309 gst_ttml_render_unified_element_copy (const UnifiedElement * unified_element)
1310 {
1311 UnifiedElement *ret;
1312
1313 if (!unified_element)
1314 return NULL;
1315
1316 ret = g_slice_new0 (UnifiedElement);
1317 ret->element = gst_subtitle_element_ref (unified_element->element);
1318 ret->pango_font_size = unified_element->pango_font_size;
1319 ret->pango_font_metrics.height = unified_element->pango_font_metrics.height;
1320 ret->pango_font_metrics.baseline =
1321 unified_element->pango_font_metrics.baseline;
1322 ret->text = g_strdup (unified_element->text);
1323
1324 return ret;
1325 }
1326
1327
1328 static void
gst_ttml_render_unified_block_free(UnifiedBlock * unified_block)1329 gst_ttml_render_unified_block_free (UnifiedBlock * unified_block)
1330 {
1331 if (!unified_block)
1332 return;
1333
1334 gst_subtitle_style_set_unref (unified_block->style_set);
1335 g_ptr_array_unref (unified_block->unified_elements);
1336 g_free (unified_block->joined_text);
1337 g_slice_free (UnifiedBlock, unified_block);
1338 }
1339
1340
1341 static UnifiedElement *
gst_ttml_render_unified_block_get_element(const UnifiedBlock * block,guint index)1342 gst_ttml_render_unified_block_get_element (const UnifiedBlock * block,
1343 guint index)
1344 {
1345 if (index >= block->unified_elements->len)
1346 return NULL;
1347 else
1348 return g_ptr_array_index (block->unified_elements, index);
1349 }
1350
1351
1352 static UnifiedBlock *
gst_ttml_render_unified_block_copy(const UnifiedBlock * block)1353 gst_ttml_render_unified_block_copy (const UnifiedBlock * block)
1354 {
1355 UnifiedBlock *ret;
1356 gint i;
1357
1358 if (!block)
1359 return NULL;
1360
1361 ret = g_slice_new0 (UnifiedBlock);
1362 ret->joined_text = g_strdup (block->joined_text);
1363 ret->style_set = gst_subtitle_style_set_ref (block->style_set);
1364 ret->unified_elements = g_ptr_array_new_with_free_func ((GDestroyNotify)
1365 gst_ttml_render_unified_element_free);
1366
1367 for (i = 0; i < block->unified_elements->len; ++i) {
1368 UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
1369 UnifiedElement *ue_copy = gst_ttml_render_unified_element_copy (ue);
1370 g_ptr_array_add (ret->unified_elements, ue_copy);
1371 }
1372
1373 return ret;
1374 }
1375
1376
1377 static guint
gst_ttml_render_unified_block_element_count(const UnifiedBlock * block)1378 gst_ttml_render_unified_block_element_count (const UnifiedBlock * block)
1379 {
1380 return block->unified_elements->len;
1381 }
1382
1383
1384 /*
1385 * Generates pango-markup'd version of @text that would make pango render it
1386 * with the styling specified by @style_set.
1387 */
1388 static gchar *
gst_ttml_render_generate_pango_markup(GstSubtitleStyleSet * style_set,guint font_height,const gchar * text)1389 gst_ttml_render_generate_pango_markup (GstSubtitleStyleSet * style_set,
1390 guint font_height, const gchar * text)
1391 {
1392 gchar *ret, *font_family, *font_size, *fgcolor;
1393 const gchar *font_style, *font_weight, *underline;
1394 gchar *escaped_text = g_markup_escape_text (text, -1);
1395
1396 fgcolor = gst_ttml_render_color_to_string (style_set->color);
1397 font_size = g_strdup_printf ("%u", font_height);
1398 font_family =
1399 gst_ttml_render_resolve_generic_fontname (style_set->font_family);
1400 if (!font_family)
1401 font_family = g_strdup (style_set->font_family);
1402 font_style = (style_set->font_style ==
1403 GST_SUBTITLE_FONT_STYLE_NORMAL) ? "normal" : "italic";
1404 font_weight = (style_set->font_weight ==
1405 GST_SUBTITLE_FONT_WEIGHT_NORMAL) ? "normal" : "bold";
1406 underline = (style_set->text_decoration ==
1407 GST_SUBTITLE_TEXT_DECORATION_UNDERLINE) ? "single" : "none";
1408
1409 ret = g_strconcat ("<span "
1410 "fgcolor=\"", fgcolor, "\" ",
1411 "font=\"", font_size, "px\" ",
1412 "font_family=\"", font_family, "\" ",
1413 "font_style=\"", font_style, "\" ",
1414 "font_weight=\"", font_weight, "\" ",
1415 "underline=\"", underline, "\" ", ">", escaped_text, "</span>", NULL);
1416
1417 g_free (fgcolor);
1418 g_free (font_family);
1419 g_free (font_size);
1420 g_free (escaped_text);
1421 return ret;
1422 }
1423
1424
1425 /*
1426 * Unfortunately, pango does not expose accurate metrics about fonts (their
1427 * maximum height and baseline position), so we need to calculate this
1428 * information ourselves by examining the ink rectangle of a string containing
1429 * characters that extend to the maximum height/depth of the font.
1430 */
1431 static FontMetrics
gst_ttml_render_get_pango_font_metrics(GstTtmlRender * render,GstSubtitleStyleSet * style_set,guint font_size)1432 gst_ttml_render_get_pango_font_metrics (GstTtmlRender * render,
1433 GstSubtitleStyleSet * style_set, guint font_size)
1434 {
1435 PangoRectangle ink_rect;
1436 gchar *string;
1437 FontMetrics ret;
1438
1439 string = gst_ttml_render_generate_pango_markup (style_set, font_size,
1440 "Áĺľď¿gqy");
1441 pango_layout_set_markup (render->layout, string, strlen (string));
1442 pango_layout_get_pixel_extents (render->layout, &ink_rect, NULL);
1443 g_free (string);
1444
1445 ret.height = ink_rect.height;
1446 ret.baseline = PANGO_PIXELS (pango_layout_get_baseline (render->layout))
1447 - ink_rect.y;
1448 return ret;
1449 }
1450
1451
1452 /*
1453 * Return the font size that you would need to pass to pango in order that the
1454 * font applied to @element would be rendered at the text height applied to
1455 * @element.
1456 */
1457 static guint
gst_ttml_render_get_pango_font_size(GstTtmlRender * render,const GstSubtitleElement * element)1458 gst_ttml_render_get_pango_font_size (GstTtmlRender * render,
1459 const GstSubtitleElement * element)
1460 {
1461 guint desired_font_size =
1462 (guint) ceil (element->style_set->font_size * render->height);
1463 guint font_size = desired_font_size;
1464 guint rendered_height = G_MAXUINT;
1465 FontMetrics metrics;
1466
1467 while (rendered_height > desired_font_size) {
1468 metrics =
1469 gst_ttml_render_get_pango_font_metrics (render, element->style_set,
1470 font_size);
1471 rendered_height = metrics.height;
1472 --font_size;
1473 }
1474
1475 return font_size + 1;
1476 }
1477
1478
1479 /*
1480 * Reunites each element in @block with its text, as extracted from @buf. Also
1481 * stores the concatenated text from all contained elements to facilitate
1482 * future processing.
1483 */
1484 static UnifiedBlock *
gst_ttml_render_unify_block(GstTtmlRender * render,const GstSubtitleBlock * block,GstBuffer * buf)1485 gst_ttml_render_unify_block (GstTtmlRender * render,
1486 const GstSubtitleBlock * block, GstBuffer * buf)
1487 {
1488 UnifiedBlock *ret = g_slice_new0 (UnifiedBlock);
1489 guint i;
1490
1491 ret->unified_elements = g_ptr_array_new_with_free_func ((GDestroyNotify)
1492 gst_ttml_render_unified_element_free);
1493 ret->style_set = gst_subtitle_style_set_ref (block->style_set);
1494 ret->joined_text = g_strdup ("");
1495
1496 for (i = 0; i < gst_subtitle_block_get_element_count (block); ++i) {
1497 gchar *text;
1498 UnifiedElement *ue = g_slice_new0 (UnifiedElement);
1499 ue->element =
1500 gst_subtitle_element_ref (gst_subtitle_block_get_element (block, i));
1501 ue->pango_font_size =
1502 gst_ttml_render_get_pango_font_size (render, ue->element);
1503 ue->pango_font_metrics =
1504 gst_ttml_render_get_pango_font_metrics (render, ue->element->style_set,
1505 ue->pango_font_size);
1506 ue->text =
1507 gst_ttml_render_get_text_from_buffer (buf, ue->element->text_index);
1508 g_ptr_array_add (ret->unified_elements, ue);
1509
1510 text = g_strjoin (NULL, ret->joined_text, ue->text, NULL);
1511 g_free (ret->joined_text);
1512 ret->joined_text = text;
1513 }
1514
1515 return ret;
1516 }
1517
1518
1519 /*
1520 * Returns index of nearest breakpoint before @index in @block's text. If no
1521 * breakpoints are found, returns -1.
1522 */
1523 static gint
gst_ttml_render_get_nearest_breakpoint(const UnifiedBlock * block,guint index)1524 gst_ttml_render_get_nearest_breakpoint (const UnifiedBlock * block, guint index)
1525 {
1526 const gchar *end = block->joined_text + index - 1;
1527
1528 while ((end = g_utf8_find_prev_char (block->joined_text, end))) {
1529 gchar buf[6] = { 0 };
1530 gunichar u = g_utf8_get_char (end);
1531 gint nbytes = g_unichar_to_utf8 (u, buf);
1532
1533 if (nbytes == 1 && (buf[0] == 0x20 || buf[0] == 0x9 || buf[0] == 0xD))
1534 return end - block->joined_text;
1535 }
1536
1537 return -1;
1538 }
1539
1540
1541 /* Return the pango markup representation of all the elements in @block. */
1542 static gchar *
gst_ttml_render_generate_block_markup(const UnifiedBlock * block)1543 gst_ttml_render_generate_block_markup (const UnifiedBlock * block)
1544 {
1545 gchar *joined_text, *old_text;
1546 guint element_count = gst_ttml_render_unified_block_element_count (block);
1547 guint i;
1548
1549 joined_text = g_strdup ("");
1550
1551 for (i = 0; i < element_count; ++i) {
1552 UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
1553 gchar *element_markup =
1554 gst_ttml_render_generate_pango_markup (ue->element->style_set,
1555 ue->pango_font_size, ue->text);
1556
1557 old_text = joined_text;
1558 joined_text = g_strconcat (joined_text, element_markup, NULL);
1559 GST_CAT_DEBUG (ttmlrender_debug, "Joined text is now: %s", joined_text);
1560
1561 g_free (element_markup);
1562 g_free (old_text);
1563 }
1564
1565 return joined_text;
1566 }
1567
1568
1569 /*
1570 * Returns a set of character ranges, which correspond to the ranges of
1571 * characters from @block that should be rendered on each generated line area.
1572 * Essentially, this function determines line breaking and wrapping.
1573 */
1574 static GPtrArray *
gst_ttml_render_get_line_char_ranges(GstTtmlRender * render,const UnifiedBlock * block,guint width,gboolean wrap)1575 gst_ttml_render_get_line_char_ranges (GstTtmlRender * render,
1576 const UnifiedBlock * block, guint width, gboolean wrap)
1577 {
1578 gint start_index = 0;
1579 GPtrArray *line_ranges = g_ptr_array_new_with_free_func ((GDestroyNotify)
1580 gst_ttml_render_char_range_free);
1581 PangoRectangle ink_rect;
1582 gchar *markup;
1583 gint i;
1584
1585 /* Handle hard breaks in block text. */
1586 while (start_index < strlen (block->joined_text)) {
1587 CharRange *range = g_slice_new0 (CharRange);
1588 gchar *c = block->joined_text + start_index;
1589 while (*c != '\0' && *c != '\n')
1590 ++c;
1591 range->first_index = start_index;
1592 range->last_index = (c - block->joined_text) - 1;
1593 g_ptr_array_add (line_ranges, range);
1594 start_index = range->last_index + 2;
1595 }
1596
1597 if (!wrap)
1598 return line_ranges;
1599
1600 GST_CAT_LOG (ttmlrender_debug,
1601 "After handling breaks, we have the following ranges:");
1602 for (i = 0; i < line_ranges->len; ++i) {
1603 CharRange *range = g_ptr_array_index (line_ranges, i);
1604 GST_CAT_LOG (ttmlrender_debug, "ranges[%d] first:%u last:%u", i,
1605 range->first_index, range->last_index);
1606 }
1607
1608 markup = gst_ttml_render_generate_block_markup (block);
1609 pango_layout_set_markup (render->layout, markup, strlen (markup));
1610 pango_layout_set_width (render->layout, -1);
1611
1612 pango_layout_get_pixel_extents (render->layout, &ink_rect, NULL);
1613 GST_CAT_LOG (ttmlrender_debug, "Layout extents - x:%d y:%d w:%d h:%d",
1614 ink_rect.x, ink_rect.y, ink_rect.width, ink_rect.height);
1615
1616 /* For each range, wrap if it extends beyond allowed width. */
1617 for (i = 0; i < line_ranges->len; ++i) {
1618 CharRange *range, *new_range;
1619 gint max_line_extent;
1620 gint end_index = 0;
1621 gint trailing;
1622 PangoRectangle rect;
1623 gboolean within_line;
1624
1625 do {
1626 range = g_ptr_array_index (line_ranges, i);
1627 GST_CAT_LOG (ttmlrender_debug,
1628 "Seeing if we need to wrap range[%d] - start:%u end:%u", i,
1629 range->first_index, range->last_index);
1630
1631 pango_layout_index_to_pos (render->layout, range->first_index, &rect);
1632 GST_CAT_LOG (ttmlrender_debug, "First char at x:%d y:%d", rect.x,
1633 rect.y);
1634
1635 max_line_extent = rect.x + (PANGO_SCALE * width);
1636 GST_CAT_LOG (ttmlrender_debug, "max_line_extent: %d",
1637 PANGO_PIXELS (max_line_extent));
1638
1639 within_line =
1640 pango_layout_xy_to_index (render->layout, max_line_extent, rect.y,
1641 &end_index, &trailing);
1642
1643 GST_CAT_LOG (ttmlrender_debug, "Index nearest to breakpoint: %d",
1644 end_index);
1645
1646 if (within_line) {
1647 end_index = gst_ttml_render_get_nearest_breakpoint (block, end_index);
1648
1649 if (end_index > range->first_index) {
1650 new_range = g_slice_new0 (CharRange);
1651 new_range->first_index = end_index + 1;
1652 new_range->last_index = range->last_index;
1653 GST_CAT_LOG (ttmlrender_debug,
1654 "Wrapping line %d; added new range - start:%u end:%u", i,
1655 new_range->first_index, new_range->last_index);
1656
1657 range->last_index = end_index;
1658 GST_CAT_LOG (ttmlrender_debug,
1659 "Modified last_index of existing range; range is now start:%u "
1660 "end:%u", range->first_index, range->last_index);
1661
1662 g_ptr_array_insert (line_ranges, ++i, new_range);
1663 } else {
1664 GST_CAT_DEBUG (ttmlrender_debug,
1665 "Couldn't find a suitable breakpoint");
1666 within_line = FALSE;
1667 }
1668 }
1669 } while (within_line);
1670 }
1671
1672 g_free (markup);
1673 return line_ranges;
1674 }
1675
1676
1677 /*
1678 * Returns the index of the element in @block containing the character at index
1679 * @char_index in @block's text. If @offset is not NULL, sets it to the
1680 * character offset of @char_index within the element where it is found.
1681 */
1682 static gint
gst_ttml_render_get_element_index(const UnifiedBlock * block,const gint char_index,gint * offset)1683 gst_ttml_render_get_element_index (const UnifiedBlock * block,
1684 const gint char_index, gint * offset)
1685 {
1686 gint count = 0;
1687 gint i;
1688
1689 if ((char_index < 0) || (char_index >= strlen (block->joined_text)))
1690 return -1;
1691
1692 for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) {
1693 UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
1694 if ((char_index >= count) && (char_index < (count + strlen (ue->text)))) {
1695 if (offset)
1696 *offset = char_index - count;
1697 break;
1698 }
1699 count += strlen (ue->text);
1700 }
1701
1702 return i;
1703 }
1704
1705
1706 static guint
gst_ttml_render_strip_leading_spaces(gchar ** string)1707 gst_ttml_render_strip_leading_spaces (gchar ** string)
1708 {
1709 gchar *c = *string;
1710
1711 while (c) {
1712 gchar buf[6] = { 0 };
1713 gunichar u = g_utf8_get_char (c);
1714 gint nbytes = g_unichar_to_utf8 (u, buf);
1715
1716 if ((nbytes == 1) && (buf[0] == 0x20))
1717 c = g_utf8_find_next_char (c, c + strlen (*string));
1718 else
1719 break;
1720 }
1721
1722 if (!c) {
1723 GST_CAT_DEBUG (ttmlrender_debug,
1724 "All characters would be removed from string.");
1725 return 0;
1726 } else if (c > *string) {
1727 gchar *tmp = *string;
1728 *string = g_strdup (c);
1729 GST_CAT_DEBUG (ttmlrender_debug, "Replacing text \"%s\" with \"%s\"", tmp,
1730 *string);
1731 g_free (tmp);
1732 }
1733
1734 return strlen (*string);
1735 }
1736
1737
1738 static guint
gst_ttml_render_strip_trailing_spaces(gchar ** string)1739 gst_ttml_render_strip_trailing_spaces (gchar ** string)
1740 {
1741 gchar *c = *string + strlen (*string) - 1;
1742 gint nbytes;
1743
1744 while (c) {
1745 gchar buf[6] = { 0 };
1746 gunichar u = g_utf8_get_char (c);
1747 nbytes = g_unichar_to_utf8 (u, buf);
1748
1749 if ((nbytes == 1) && (buf[0] == 0x20))
1750 c = g_utf8_find_prev_char (*string, c);
1751 else
1752 break;
1753 }
1754
1755 if (!c) {
1756 GST_CAT_DEBUG (ttmlrender_debug,
1757 "All characters would be removed from string.");
1758 return 0;
1759 } else {
1760 gchar *tmp = *string;
1761 *string = g_strndup (*string, (c - *string) + nbytes);
1762 GST_CAT_DEBUG (ttmlrender_debug, "Replacing text \"%s\" with \"%s\"", tmp,
1763 *string);
1764 g_free (tmp);
1765 }
1766
1767 return strlen (*string);
1768 }
1769
1770
1771 /*
1772 * Treating each block in @blocks as a separate line area, conditionally strips
1773 * space characters from the beginning and end of each line. This function
1774 * implements the suppress-at-line-break="auto" and
1775 * white-space-treatment="ignore-if-surrounding-linefeed" behaviours (specified
1776 * by TTML section 7.2.3) for elements at the start and end of lines that have
1777 * xml:space="default" applied to them. If stripping whitespace from a block
1778 * removes all elements of that block, the block will be removed from @blocks.
1779 * Returns the number of remaining blocks.
1780 */
1781 static guint
gst_ttml_render_handle_whitespace(GPtrArray * blocks)1782 gst_ttml_render_handle_whitespace (GPtrArray * blocks)
1783 {
1784 gint i;
1785
1786 for (i = 0; i < blocks->len; ++i) {
1787 UnifiedBlock *ub = g_ptr_array_index (blocks, i);
1788 UnifiedElement *ue;
1789 guint remaining_chars = 0;
1790
1791 /* Remove leading spaces from line area. */
1792 while ((gst_ttml_render_unified_block_element_count (ub) > 0)
1793 && (remaining_chars == 0)) {
1794 ue = gst_ttml_render_unified_block_get_element (ub, 0);
1795 if (!ue->element->suppress_whitespace)
1796 break;
1797 remaining_chars = gst_ttml_render_strip_leading_spaces (&ue->text);
1798
1799 if (remaining_chars == 0) {
1800 g_ptr_array_remove_index (ub->unified_elements, 0);
1801 GST_CAT_DEBUG (ttmlrender_debug, "Removed first element from block");
1802 }
1803 }
1804
1805 remaining_chars = 0;
1806
1807 /* Remove trailing spaces from line area. */
1808 while ((gst_ttml_render_unified_block_element_count (ub) > 0)
1809 && (remaining_chars == 0)) {
1810 ue = gst_ttml_render_unified_block_get_element (ub,
1811 gst_ttml_render_unified_block_element_count (ub) - 1);
1812 if (!ue->element->suppress_whitespace)
1813 break;
1814 remaining_chars = gst_ttml_render_strip_trailing_spaces (&ue->text);
1815
1816 if (remaining_chars == 0) {
1817 g_ptr_array_remove_index (ub->unified_elements,
1818 gst_ttml_render_unified_block_element_count (ub) - 1);
1819 GST_CAT_DEBUG (ttmlrender_debug, "Removed last element from block");
1820 }
1821 }
1822
1823 if (gst_ttml_render_unified_block_element_count (ub) == 0)
1824 g_ptr_array_remove_index (blocks, i--);
1825 }
1826
1827 return blocks->len;
1828 }
1829
1830
1831 /*
1832 * Splits a single UnifiedBlock, @block, into an array of separate
1833 * UnifiedBlocks, according to the character ranges given in @char_ranges.
1834 * Each resulting UnifiedBlock will contain only the elements to which belong
1835 * the characters in its corresponding character range; the text of the first
1836 * and last element in the block will be clipped of any characters before and
1837 * after, respectively, the first and last characters in the corresponding
1838 * range.
1839 */
1840 static GPtrArray *
gst_ttml_render_split_block(UnifiedBlock * block,GPtrArray * char_ranges)1841 gst_ttml_render_split_block (UnifiedBlock * block, GPtrArray * char_ranges)
1842 {
1843 GPtrArray *ret = g_ptr_array_new_with_free_func ((GDestroyNotify)
1844 gst_ttml_render_unified_block_free);
1845 gint i;
1846
1847 for (i = 0; i < char_ranges->len; ++i) {
1848 gint index;
1849 gint first_offset = 0;
1850 gint last_offset = 0;
1851 CharRange *range = g_ptr_array_index (char_ranges, i);
1852 UnifiedBlock *clone = gst_ttml_render_unified_block_copy (block);
1853 UnifiedElement *ue;
1854 gchar *tmp;
1855
1856 GST_CAT_LOG (ttmlrender_debug, "range start:%u end:%u", range->first_index,
1857 range->last_index);
1858 index =
1859 gst_ttml_render_get_element_index (clone, range->last_index,
1860 &last_offset);
1861 GST_CAT_LOG (ttmlrender_debug, "Last char in range is in element %d",
1862 index);
1863
1864 if (index < 0) {
1865 GST_CAT_WARNING (ttmlrender_debug, "Range end not found in block text.");
1866 gst_ttml_render_unified_block_free (clone);
1867 continue;
1868 }
1869
1870 /* Remove elements that are after the one that contains the range end. */
1871 GST_CAT_LOG (ttmlrender_debug, "There are %d elements in cloned block.",
1872 gst_ttml_render_unified_block_element_count (clone));
1873 while (gst_ttml_render_unified_block_element_count (clone) > (index + 1)) {
1874 GST_CAT_LOG (ttmlrender_debug, "Removing last element in cloned block.");
1875 g_ptr_array_remove_index (clone->unified_elements, index + 1);
1876 }
1877
1878 index =
1879 gst_ttml_render_get_element_index (clone, range->first_index,
1880 &first_offset);
1881 GST_CAT_LOG (ttmlrender_debug, "First char in range is in element %d",
1882 index);
1883
1884 if (index < 0) {
1885 GST_CAT_WARNING (ttmlrender_debug,
1886 "Range start not found in block text.");
1887 gst_ttml_render_unified_block_free (clone);
1888 continue;
1889 }
1890
1891 /* Remove elements that are before the one that contains the range start. */
1892 while (index > 0) {
1893 GST_CAT_LOG (ttmlrender_debug, "Removing first element in cloned block");
1894 g_ptr_array_remove_index (clone->unified_elements, 0);
1895 --index;
1896 }
1897
1898 /* Remove characters from first element that are before the range start. */
1899 ue = gst_ttml_render_unified_block_get_element (clone, 0);
1900 if (first_offset > 0) {
1901 tmp = ue->text;
1902 ue->text = g_strdup (ue->text + first_offset);
1903 GST_CAT_DEBUG (ttmlrender_debug,
1904 "First element text has been clipped to \"%s\"", ue->text);
1905 g_free (tmp);
1906
1907 if (gst_ttml_render_unified_block_element_count (clone) == 1)
1908 last_offset -= first_offset;
1909 }
1910
1911 /* Remove characters from last element that are after the range end. */
1912 ue = gst_ttml_render_unified_block_get_element (clone,
1913 gst_ttml_render_unified_block_element_count (clone) - 1);
1914 if (last_offset < (strlen (ue->text) - 1)) {
1915 tmp = ue->text;
1916 ue->text = g_strndup (ue->text, last_offset + 1);
1917 GST_CAT_DEBUG (ttmlrender_debug,
1918 "Last element text has been clipped to \"%s\"", ue->text);
1919 g_free (tmp);
1920 }
1921
1922 if (gst_ttml_render_unified_block_element_count (clone) > 0)
1923 g_ptr_array_add (ret, clone);
1924 else
1925 gst_ttml_render_unified_block_free (clone);
1926 }
1927
1928 if (ret->len == 0) {
1929 GST_CAT_DEBUG (ttmlrender_debug, "No elements remain in clone.");
1930 g_ptr_array_unref (ret);
1931 ret = NULL;
1932 }
1933 return ret;
1934 }
1935
1936
1937 /* Render the text in a pango-markup string. */
1938 static GstTtmlRenderRenderedImage *
gst_ttml_render_draw_text(GstTtmlRender * render,const gchar * text,guint line_height,guint baseline_offset)1939 gst_ttml_render_draw_text (GstTtmlRender * render, const gchar * text,
1940 guint line_height, guint baseline_offset)
1941 {
1942 GstTtmlRenderRenderedImage *ret;
1943 cairo_surface_t *surface, *cropped_surface;
1944 cairo_t *cairo_state, *cropped_state;
1945 GstMapInfo map;
1946 PangoRectangle logical_rect, ink_rect;
1947 guint buf_width, buf_height;
1948 gint stride;
1949 gint bounding_box_x1, bounding_box_x2, bounding_box_y1, bounding_box_y2;
1950 gint baseline;
1951
1952 ret = gst_ttml_render_rendered_image_new_empty ();
1953
1954 pango_layout_set_markup (render->layout, text, strlen (text));
1955 GST_CAT_DEBUG (ttmlrender_debug, "Layout text: \"%s\"",
1956 pango_layout_get_text (render->layout));
1957 pango_layout_set_width (render->layout, -1);
1958
1959 pango_layout_get_pixel_extents (render->layout, &ink_rect, &logical_rect);
1960
1961 baseline = PANGO_PIXELS (pango_layout_get_baseline (render->layout));
1962
1963 bounding_box_x1 = MIN (logical_rect.x, ink_rect.x);
1964 bounding_box_x2 = MAX (logical_rect.x + logical_rect.width,
1965 ink_rect.x + ink_rect.width);
1966 bounding_box_y1 = MIN (logical_rect.y, ink_rect.y);
1967 bounding_box_y2 = MAX (logical_rect.y + logical_rect.height,
1968 ink_rect.y + ink_rect.height);
1969
1970 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
1971 (bounding_box_x2 - bounding_box_x1), (bounding_box_y2 - bounding_box_y1));
1972 cairo_state = cairo_create (surface);
1973 cairo_set_operator (cairo_state, CAIRO_OPERATOR_CLEAR);
1974 cairo_paint (cairo_state);
1975 cairo_set_operator (cairo_state, CAIRO_OPERATOR_OVER);
1976
1977 cairo_save (cairo_state);
1978 pango_cairo_show_layout (cairo_state, render->layout);
1979 cairo_restore (cairo_state);
1980
1981 buf_width = bounding_box_x2 - bounding_box_x1;
1982 buf_height = ink_rect.height;
1983 GST_CAT_DEBUG (ttmlrender_debug, "Output buffer width: %u height: %u",
1984 buf_width, buf_height);
1985
1986 ret->image = gst_buffer_new_allocate (NULL, 4 * buf_width * buf_height, NULL);
1987 gst_buffer_memset (ret->image, 0, 0U, 4 * buf_width * buf_height);
1988 gst_buffer_map (ret->image, &map, GST_MAP_READWRITE);
1989
1990 stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, buf_width);
1991 cropped_surface =
1992 cairo_image_surface_create_for_data (map.data, CAIRO_FORMAT_ARGB32,
1993 (bounding_box_x2 - bounding_box_x1), ink_rect.height, stride);
1994 cropped_state = cairo_create (cropped_surface);
1995 cairo_set_source_surface (cropped_state, surface, -bounding_box_x1,
1996 -ink_rect.y);
1997 cairo_rectangle (cropped_state, 0, 0, buf_width, buf_height);
1998 cairo_fill (cropped_state);
1999
2000 cairo_destroy (cairo_state);
2001 cairo_surface_destroy (surface);
2002 cairo_destroy (cropped_state);
2003 cairo_surface_destroy (cropped_surface);
2004 gst_buffer_unmap (ret->image, &map);
2005
2006 ret->width = buf_width;
2007 ret->height = buf_height;
2008 ret->x = 0;
2009 ret->y = MAX (0, (gint) baseline_offset - (baseline - ink_rect.y));
2010 return ret;
2011 }
2012
2013
2014 static GstTtmlRenderRenderedImage *
gst_ttml_render_render_block_elements(GstTtmlRender * render,UnifiedBlock * block,BlockMetrics block_metrics)2015 gst_ttml_render_render_block_elements (GstTtmlRender * render,
2016 UnifiedBlock * block, BlockMetrics block_metrics)
2017 {
2018 GPtrArray *inline_images = g_ptr_array_new_with_free_func (
2019 (GDestroyNotify) gst_ttml_render_rendered_image_free);
2020 GstTtmlRenderRenderedImage *ret = NULL;
2021 guint line_padding =
2022 (guint) ceil (block->style_set->line_padding * render->width);
2023 gint i;
2024
2025 for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) {
2026 UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
2027 gchar *markup;
2028 GstTtmlRenderRenderedImage *text_image, *bg_image, *combined_image;
2029 guint bg_offset, bg_width, bg_height;
2030 GstBuffer *background;
2031
2032 markup = gst_ttml_render_generate_pango_markup (ue->element->style_set,
2033 ue->pango_font_size, ue->text);
2034 text_image = gst_ttml_render_draw_text (render, markup,
2035 block_metrics.line_height, block_metrics.baseline_offset);
2036 g_free (markup);
2037
2038 if (!block->style_set->fill_line_gap) {
2039 bg_offset =
2040 block_metrics.baseline_offset - ue->pango_font_metrics.baseline;
2041 bg_height = ue->pango_font_metrics.height;
2042 } else {
2043 bg_offset = 0;
2044 bg_height = block_metrics.line_height;
2045 }
2046 bg_width = text_image->width;
2047
2048 if (line_padding > 0) {
2049 if (i == 0) {
2050 text_image->x += line_padding;
2051 bg_width += line_padding;
2052 }
2053 if (i == (gst_ttml_render_unified_block_element_count (block) - 1))
2054 bg_width += line_padding;
2055 }
2056
2057 background = gst_ttml_render_draw_rectangle (bg_width, bg_height,
2058 ue->element->style_set->background_color);
2059 bg_image = gst_ttml_render_rendered_image_new (background, 0,
2060 bg_offset, bg_width, bg_height);
2061 combined_image = gst_ttml_render_rendered_image_combine (bg_image,
2062 text_image);
2063 gst_ttml_render_rendered_image_free (bg_image);
2064 gst_ttml_render_rendered_image_free (text_image);
2065 g_ptr_array_add (inline_images, combined_image);
2066 }
2067
2068 ret = gst_ttml_render_stitch_images (inline_images,
2069 GST_TTML_DIRECTION_INLINE);
2070 GST_CAT_DEBUG (ttmlrender_debug,
2071 "Stitched line image - x:%d y:%d w:%u h:%u",
2072 ret->x, ret->y, ret->width, ret->height);
2073 g_ptr_array_unref (inline_images);
2074 return ret;
2075 }
2076
2077
2078 /*
2079 * Align the images in @lines according to the multi_row_align and text_align
2080 * settings in @style_set.
2081 */
2082 static void
gst_ttml_render_align_line_areas(GPtrArray * lines,const GstSubtitleStyleSet * style_set)2083 gst_ttml_render_align_line_areas (GPtrArray * lines,
2084 const GstSubtitleStyleSet * style_set)
2085 {
2086 guint longest_line_width = 0;
2087 gint i;
2088
2089 for (i = 0; i < lines->len; ++i) {
2090 GstTtmlRenderRenderedImage *line = g_ptr_array_index (lines, i);
2091 if (line->width > longest_line_width)
2092 longest_line_width = line->width;
2093 }
2094
2095 for (i = 0; i < lines->len; ++i) {
2096 GstTtmlRenderRenderedImage *line = g_ptr_array_index (lines, i);
2097
2098 switch (style_set->multi_row_align) {
2099 case GST_SUBTITLE_MULTI_ROW_ALIGN_CENTER:
2100 line->x += (gint) round ((longest_line_width - line->width) / 2.0);
2101 break;
2102 case GST_SUBTITLE_MULTI_ROW_ALIGN_END:
2103 line->x += (longest_line_width - line->width);
2104 break;
2105 case GST_SUBTITLE_MULTI_ROW_ALIGN_AUTO:
2106 switch (style_set->text_align) {
2107 case GST_SUBTITLE_TEXT_ALIGN_CENTER:
2108 line->x += (gint) round ((longest_line_width - line->width) / 2.0);
2109 break;
2110 case GST_SUBTITLE_TEXT_ALIGN_END:
2111 case GST_SUBTITLE_TEXT_ALIGN_RIGHT:
2112 line->x += (longest_line_width - line->width);
2113 break;
2114 default:
2115 break;
2116 }
2117 break;
2118 default:
2119 break;
2120 }
2121 }
2122 }
2123
2124
2125 /*
2126 * Renders each UnifiedBlock in @blocks, and sets the positions of the
2127 * resulting images according to the line height in @metrics and the alignment
2128 * settings in @style_set.
2129 */
2130 static GPtrArray *
gst_ttml_render_layout_blocks(GstTtmlRender * render,GPtrArray * blocks,BlockMetrics metrics,const GstSubtitleStyleSet * style_set)2131 gst_ttml_render_layout_blocks (GstTtmlRender * render, GPtrArray * blocks,
2132 BlockMetrics metrics, const GstSubtitleStyleSet * style_set)
2133 {
2134 GPtrArray *ret = g_ptr_array_new_with_free_func ((GDestroyNotify)
2135 gst_ttml_render_rendered_image_free);
2136 gint i;
2137
2138 for (i = 0; i < blocks->len; ++i) {
2139 UnifiedBlock *block = g_ptr_array_index (blocks, i);
2140
2141 GstTtmlRenderRenderedImage *line =
2142 gst_ttml_render_render_block_elements (render, block,
2143 metrics);
2144 line->y += (i * metrics.line_height);
2145 g_ptr_array_add (ret, line);
2146 }
2147
2148 gst_ttml_render_align_line_areas (ret, style_set);
2149 return ret;
2150 }
2151
2152
2153 /* If any of an array of elements has line wrapping enabled, returns TRUE. */
2154 static gboolean
gst_ttml_render_elements_are_wrapped(GPtrArray * elements)2155 gst_ttml_render_elements_are_wrapped (GPtrArray * elements)
2156 {
2157 GstSubtitleElement *element;
2158 guint i;
2159
2160 for (i = 0; i < elements->len; ++i) {
2161 element = g_ptr_array_index (elements, i);
2162 if (element->style_set->wrap_option == GST_SUBTITLE_WRAPPING_ON)
2163 return TRUE;
2164 }
2165
2166 return FALSE;
2167 }
2168
2169
2170 /*
2171 * Return the descender (in pixels) shared by the greatest number of glyphs in
2172 * @block.
2173 */
2174 static guint
gst_ttml_render_get_most_frequent_descender(GstTtmlRender * render,UnifiedBlock * block)2175 gst_ttml_render_get_most_frequent_descender (GstTtmlRender * render,
2176 UnifiedBlock * block)
2177 {
2178 GHashTable *count_table = g_hash_table_new (g_direct_hash, g_direct_equal);
2179 GHashTableIter iter;
2180 gpointer key, value;
2181 guint max_count = 0;
2182 guint ret = 0;
2183 gint i;
2184
2185 for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) {
2186 UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
2187 guint descender =
2188 ue->pango_font_metrics.height - ue->pango_font_metrics.baseline;
2189 guint count;
2190
2191 if (g_hash_table_contains (count_table, GUINT_TO_POINTER (descender))) {
2192 count = GPOINTER_TO_UINT (g_hash_table_lookup (count_table,
2193 GUINT_TO_POINTER (descender)));
2194 GST_CAT_LOG (ttmlrender_debug,
2195 "Table already contains %u glyphs with descender %u; increasing "
2196 "that count to %ld", count, descender,
2197 count + g_utf8_strlen (ue->text, -1));
2198 count += g_utf8_strlen (ue->text, -1);
2199 } else {
2200 count = g_utf8_strlen (ue->text, -1);
2201 GST_CAT_LOG (ttmlrender_debug,
2202 "No glyphs with descender %u; adding entry to table with count of %u",
2203 descender, count);
2204 }
2205
2206 g_hash_table_insert (count_table,
2207 GUINT_TO_POINTER (descender), GUINT_TO_POINTER (count));
2208 }
2209
2210 g_hash_table_iter_init (&iter, count_table);
2211 while (g_hash_table_iter_next (&iter, &key, &value)) {
2212 guint descender = GPOINTER_TO_UINT (key);
2213 guint count = GPOINTER_TO_UINT (value);
2214
2215 if (count > max_count) {
2216 max_count = count;
2217 ret = descender;
2218 }
2219 }
2220
2221 g_hash_table_unref (count_table);
2222 return ret;
2223 }
2224
2225
2226 static BlockMetrics
gst_ttml_render_get_block_metrics(GstTtmlRender * render,UnifiedBlock * block)2227 gst_ttml_render_get_block_metrics (GstTtmlRender * render, UnifiedBlock * block)
2228 {
2229 BlockMetrics ret;
2230
2231 /*
2232 * The specified behaviour in TTML when lineHeight is "normal" is different
2233 * from the behaviour when a percentage is given. In the former case, the
2234 * line height is a percentage (the TTML spec recommends 125%) of the largest
2235 * font size that is applied to the spans within the block; in the latter
2236 * case, the line height is the given percentage of the font size that is
2237 * applied to the block itself.
2238 */
2239 if (block->style_set->line_height < 0) { /* lineHeight="normal" case */
2240 guint max_text_height = 0;
2241 guint descender = 0;
2242 guint i;
2243
2244 for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) {
2245 UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
2246
2247 if (ue->pango_font_metrics.height > max_text_height) {
2248 max_text_height = ue->pango_font_metrics.height;
2249 descender =
2250 ue->pango_font_metrics.height - ue->pango_font_metrics.baseline;
2251 }
2252 }
2253
2254 GST_CAT_LOG (ttmlrender_debug, "Max descender: %u Max text height: %u",
2255 descender, max_text_height);
2256 ret.line_height = (guint) ceil (max_text_height * 1.25);
2257 ret.baseline_offset = (guint) ((max_text_height + ret.line_height) / 2.0)
2258 - descender;
2259 } else {
2260 guint descender;
2261 guint font_size;
2262
2263 descender = gst_ttml_render_get_most_frequent_descender (render, block);
2264 GST_CAT_LOG (ttmlrender_debug,
2265 "Got most frequent descender value of %u pixels.", descender);
2266 font_size = (guint) ceil (block->style_set->font_size * render->height);
2267 ret.line_height = (guint) ceil (font_size * block->style_set->line_height);
2268 ret.baseline_offset = (guint) ((font_size + ret.line_height) / 2.0)
2269 - descender;
2270 }
2271
2272 return ret;
2273 }
2274
2275
2276 static GstTtmlRenderRenderedImage *
gst_ttml_render_rendered_image_new(GstBuffer * image,gint x,gint y,guint width,guint height)2277 gst_ttml_render_rendered_image_new (GstBuffer * image, gint x, gint y,
2278 guint width, guint height)
2279 {
2280 GstTtmlRenderRenderedImage *ret;
2281
2282 ret = g_slice_new0 (GstTtmlRenderRenderedImage);
2283
2284 ret->image = image;
2285 ret->x = x;
2286 ret->y = y;
2287 ret->width = width;
2288 ret->height = height;
2289
2290 return ret;
2291 }
2292
2293
2294 static GstTtmlRenderRenderedImage *
gst_ttml_render_rendered_image_new_empty(void)2295 gst_ttml_render_rendered_image_new_empty (void)
2296 {
2297 return gst_ttml_render_rendered_image_new (NULL, 0, 0, 0, 0);
2298 }
2299
2300
2301 static inline GstTtmlRenderRenderedImage *
gst_ttml_render_rendered_image_copy(GstTtmlRenderRenderedImage * image)2302 gst_ttml_render_rendered_image_copy (GstTtmlRenderRenderedImage * image)
2303 {
2304 GstTtmlRenderRenderedImage *ret = g_slice_new0 (GstTtmlRenderRenderedImage);
2305
2306 ret->image = gst_buffer_ref (image->image);
2307 ret->x = image->x;
2308 ret->y = image->y;
2309 ret->width = image->width;
2310 ret->height = image->height;
2311
2312 return ret;
2313 }
2314
2315
2316 static void
gst_ttml_render_rendered_image_free(GstTtmlRenderRenderedImage * image)2317 gst_ttml_render_rendered_image_free (GstTtmlRenderRenderedImage * image)
2318 {
2319 if (!image)
2320 return;
2321 gst_buffer_unref (image->image);
2322 g_slice_free (GstTtmlRenderRenderedImage, image);
2323 }
2324
2325
2326 /*
2327 * Combines two rendered image into a single image. The order of arguments is
2328 * significant: @image2 will be rendered on top of @image1.
2329 */
2330 static GstTtmlRenderRenderedImage *
gst_ttml_render_rendered_image_combine(GstTtmlRenderRenderedImage * image1,GstTtmlRenderRenderedImage * image2)2331 gst_ttml_render_rendered_image_combine (GstTtmlRenderRenderedImage * image1,
2332 GstTtmlRenderRenderedImage * image2)
2333 {
2334 GstTtmlRenderRenderedImage *ret;
2335 GstMapInfo map1, map2, map_dest;
2336 cairo_surface_t *sfc1, *sfc2, *sfc_dest;
2337 cairo_t *state_dest;
2338
2339 if (!image1 && !image2)
2340 return NULL;
2341 if (image1 && !image2)
2342 return gst_ttml_render_rendered_image_copy (image1);
2343 if (image2 && !image1)
2344 return gst_ttml_render_rendered_image_copy (image2);
2345
2346 ret = g_slice_new0 (GstTtmlRenderRenderedImage);
2347
2348 /* Work out dimensions of combined image. */
2349 ret->x = MIN (image1->x, image2->x);
2350 ret->y = MIN (image1->y, image2->y);
2351 ret->width = MAX (image1->x + image1->width, image2->x + image2->width)
2352 - ret->x;
2353 ret->height = MAX (image1->y + image1->height, image2->y + image2->height)
2354 - ret->y;
2355
2356 GST_CAT_LOG (ttmlrender_debug, "Dimensions of combined image: x:%u y:%u "
2357 "width:%u height:%u", ret->x, ret->y, ret->width, ret->height);
2358
2359 /* Create cairo_surface from src images. */
2360 gst_buffer_map (image1->image, &map1, GST_MAP_READ);
2361 sfc1 =
2362 cairo_image_surface_create_for_data (map1.data, CAIRO_FORMAT_ARGB32,
2363 image1->width, image1->height,
2364 cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, image1->width));
2365
2366 gst_buffer_map (image2->image, &map2, GST_MAP_READ);
2367 sfc2 =
2368 cairo_image_surface_create_for_data (map2.data, CAIRO_FORMAT_ARGB32,
2369 image2->width, image2->height,
2370 cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, image2->width));
2371
2372 /* Create cairo_surface for resultant image. */
2373 ret->image = gst_buffer_new_allocate (NULL, 4 * ret->width * ret->height,
2374 NULL);
2375 gst_buffer_memset (ret->image, 0, 0U, 4 * ret->width * ret->height);
2376 gst_buffer_map (ret->image, &map_dest, GST_MAP_READWRITE);
2377 sfc_dest =
2378 cairo_image_surface_create_for_data (map_dest.data, CAIRO_FORMAT_ARGB32,
2379 ret->width, ret->height,
2380 cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, ret->width));
2381 state_dest = cairo_create (sfc_dest);
2382
2383 /* Blend image1 into destination surface. */
2384 cairo_set_source_surface (state_dest, sfc1, image1->x - ret->x,
2385 image1->y - ret->y);
2386 cairo_rectangle (state_dest, image1->x - ret->x, image1->y - ret->y,
2387 image1->width, image1->height);
2388 cairo_fill (state_dest);
2389
2390 /* Blend image2 into destination surface. */
2391 cairo_set_source_surface (state_dest, sfc2, image2->x - ret->x,
2392 image2->y - ret->y);
2393 cairo_rectangle (state_dest, image2->x - ret->x, image2->y - ret->y,
2394 image2->width, image2->height);
2395 cairo_fill (state_dest);
2396
2397 /* Return destination image. */
2398 cairo_destroy (state_dest);
2399 cairo_surface_destroy (sfc1);
2400 cairo_surface_destroy (sfc2);
2401 cairo_surface_destroy (sfc_dest);
2402 gst_buffer_unmap (image1->image, &map1);
2403 gst_buffer_unmap (image2->image, &map2);
2404 gst_buffer_unmap (ret->image, &map_dest);
2405
2406 return ret;
2407 }
2408
2409
2410 static GstTtmlRenderRenderedImage *
gst_ttml_render_rendered_image_crop(GstTtmlRenderRenderedImage * image,gint x,gint y,guint width,guint height)2411 gst_ttml_render_rendered_image_crop (GstTtmlRenderRenderedImage * image,
2412 gint x, gint y, guint width, guint height)
2413 {
2414 GstTtmlRenderRenderedImage *ret;
2415 GstMapInfo map_src, map_dest;
2416 cairo_surface_t *sfc_src, *sfc_dest;
2417 cairo_t *state_dest;
2418
2419 if ((x <= image->x) && (y <= image->y) && (width >= image->width)
2420 && (height >= image->height))
2421 return gst_ttml_render_rendered_image_copy (image);
2422
2423 if (image->x >= (x + (gint) width)
2424 || (image->x + (gint) image->width) <= x
2425 || image->y >= (y + (gint) height)
2426 || (image->y + (gint) image->height) <= y) {
2427 GST_CAT_WARNING (ttmlrender_debug,
2428 "Crop rectangle doesn't intersect image.");
2429 return NULL;
2430 }
2431
2432 ret = g_slice_new0 (GstTtmlRenderRenderedImage);
2433
2434 ret->x = MAX (image->x, x);
2435 ret->y = MAX (image->y, y);
2436 ret->width = MIN ((image->x + image->width) - ret->x, (x + width) - ret->x);
2437 ret->height = MIN ((image->y + image->height) - ret->y,
2438 (y + height) - ret->y);
2439
2440 GST_CAT_LOG (ttmlrender_debug, "Dimensions of cropped image: x:%u y:%u "
2441 "width:%u height:%u", ret->x, ret->y, ret->width, ret->height);
2442
2443 /* Create cairo_surface from src image. */
2444 gst_buffer_map (image->image, &map_src, GST_MAP_READ);
2445 sfc_src =
2446 cairo_image_surface_create_for_data (map_src.data, CAIRO_FORMAT_ARGB32,
2447 image->width, image->height,
2448 cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, image->width));
2449
2450 /* Create cairo_surface for cropped image. */
2451 ret->image = gst_buffer_new_allocate (NULL, 4 * ret->width * ret->height,
2452 NULL);
2453 gst_buffer_memset (ret->image, 0, 0U, 4 * ret->width * ret->height);
2454 gst_buffer_map (ret->image, &map_dest, GST_MAP_READWRITE);
2455 sfc_dest =
2456 cairo_image_surface_create_for_data (map_dest.data, CAIRO_FORMAT_ARGB32,
2457 ret->width, ret->height,
2458 cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, ret->width));
2459 state_dest = cairo_create (sfc_dest);
2460
2461 /* Copy section of image1 into destination surface. */
2462 cairo_set_source_surface (state_dest, sfc_src, (image->x - ret->x),
2463 (image->y - ret->y));
2464 cairo_rectangle (state_dest, 0, 0, ret->width, ret->height);
2465 cairo_fill (state_dest);
2466
2467 cairo_destroy (state_dest);
2468 cairo_surface_destroy (sfc_src);
2469 cairo_surface_destroy (sfc_dest);
2470 gst_buffer_unmap (image->image, &map_src);
2471 gst_buffer_unmap (ret->image, &map_dest);
2472
2473 return ret;
2474 }
2475
2476
2477 static gboolean
gst_ttml_render_color_is_transparent(GstSubtitleColor * color)2478 gst_ttml_render_color_is_transparent (GstSubtitleColor * color)
2479 {
2480 return (color->a == 0);
2481 }
2482
2483
2484 /*
2485 * Overlays a set of rendered images to return a single image. Order is
2486 * significant: later entries in @images are rendered on top of earlier
2487 * entries.
2488 */
2489 static GstTtmlRenderRenderedImage *
gst_ttml_render_overlay_images(GPtrArray * images)2490 gst_ttml_render_overlay_images (GPtrArray * images)
2491 {
2492 GstTtmlRenderRenderedImage *ret = NULL;
2493 gint i;
2494
2495 for (i = 0; i < images->len; ++i) {
2496 GstTtmlRenderRenderedImage *tmp = ret;
2497 ret = gst_ttml_render_rendered_image_combine (ret,
2498 g_ptr_array_index (images, i));
2499 gst_ttml_render_rendered_image_free (tmp);
2500 }
2501
2502 return ret;
2503 }
2504
2505
2506 /*
2507 * Takes a set of images and renders them as a single image, where all the
2508 * images are arranged contiguously in the direction given by @direction. Note
2509 * that the positions of the images in @images will be altered.
2510 */
2511 static GstTtmlRenderRenderedImage *
gst_ttml_render_stitch_images(GPtrArray * images,GstTtmlDirection direction)2512 gst_ttml_render_stitch_images (GPtrArray * images, GstTtmlDirection direction)
2513 {
2514 guint cur_offset = 0;
2515 GstTtmlRenderRenderedImage *ret = NULL;
2516 gint i;
2517
2518 for (i = 0; i < images->len; ++i) {
2519 GstTtmlRenderRenderedImage *block;
2520 block = g_ptr_array_index (images, i);
2521
2522 if (direction == GST_TTML_DIRECTION_BLOCK) {
2523 block->y += cur_offset;
2524 cur_offset = block->y + block->height;
2525 } else {
2526 block->x += cur_offset;
2527 cur_offset = block->x + block->width;
2528 }
2529 }
2530
2531 ret = gst_ttml_render_overlay_images (images);
2532
2533 if (ret) {
2534 if (direction == GST_TTML_DIRECTION_BLOCK)
2535 GST_CAT_LOG (ttmlrender_debug, "Height of stitched image: %u",
2536 ret->height);
2537 else
2538 GST_CAT_LOG (ttmlrender_debug, "Width of stitched image: %u", ret->width);
2539 ret->image = gst_buffer_make_writable (ret->image);
2540 }
2541 return ret;
2542 }
2543
2544
2545 static GstTtmlRenderRenderedImage *
gst_ttml_render_render_text_block(GstTtmlRender * render,const GstSubtitleBlock * block,GstBuffer * text_buf,guint width,gboolean overflow)2546 gst_ttml_render_render_text_block (GstTtmlRender * render,
2547 const GstSubtitleBlock * block, GstBuffer * text_buf, guint width,
2548 gboolean overflow)
2549 {
2550 UnifiedBlock *unified_block;
2551 BlockMetrics metrics;
2552 gboolean wrap;
2553 guint line_padding;
2554 GPtrArray *ranges;
2555 GPtrArray *split_blocks;
2556 GPtrArray *images;
2557 GstTtmlRenderRenderedImage *rendered_block = NULL;
2558 gint i;
2559
2560 unified_block = gst_ttml_render_unify_block (render, block, text_buf);
2561 metrics = gst_ttml_render_get_block_metrics (render, unified_block);
2562 wrap = gst_ttml_render_elements_are_wrapped (block->elements);
2563
2564 line_padding = (guint) ceil (block->style_set->line_padding * render->width);
2565 ranges = gst_ttml_render_get_line_char_ranges (render, unified_block, width -
2566 (2 * line_padding), wrap);
2567
2568 for (i = 0; i < ranges->len; ++i) {
2569 CharRange *range = g_ptr_array_index (ranges, i);
2570 GST_CAT_LOG (ttmlrender_debug, "ranges[%d] first:%u last:%u", i,
2571 range->first_index, range->last_index);
2572 }
2573
2574 split_blocks = gst_ttml_render_split_block (unified_block, ranges);
2575 if (split_blocks) {
2576 guint blocks_remining = gst_ttml_render_handle_whitespace (split_blocks);
2577 GST_CAT_DEBUG (ttmlrender_debug,
2578 "There are %u blocks remaining after whitespace handling.",
2579 blocks_remining);
2580
2581 if (blocks_remining > 0) {
2582 images = gst_ttml_render_layout_blocks (render, split_blocks, metrics,
2583 unified_block->style_set);
2584 rendered_block = gst_ttml_render_overlay_images (images);
2585 g_ptr_array_unref (images);
2586 }
2587 g_ptr_array_unref (split_blocks);
2588 }
2589
2590 g_ptr_array_unref (ranges);
2591 gst_ttml_render_unified_block_free (unified_block);
2592 return rendered_block;
2593 }
2594
2595
2596 static GstVideoOverlayComposition *
gst_ttml_render_compose_overlay(GstTtmlRenderRenderedImage * image)2597 gst_ttml_render_compose_overlay (GstTtmlRenderRenderedImage * image)
2598 {
2599 GstVideoOverlayRectangle *rectangle;
2600 GstVideoOverlayComposition *ret = NULL;
2601
2602 gst_buffer_add_video_meta (image->image, GST_VIDEO_FRAME_FLAG_NONE,
2603 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB, image->width, image->height);
2604
2605 rectangle = gst_video_overlay_rectangle_new_raw (image->image, image->x,
2606 image->y, image->width, image->height,
2607 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
2608
2609 ret = gst_video_overlay_composition_new (rectangle);
2610 gst_video_overlay_rectangle_unref (rectangle);
2611 return ret;
2612 }
2613
2614
2615 static GstVideoOverlayComposition *
gst_ttml_render_render_text_region(GstTtmlRender * render,GstSubtitleRegion * region,GstBuffer * text_buf)2616 gst_ttml_render_render_text_region (GstTtmlRender * render,
2617 GstSubtitleRegion * region, GstBuffer * text_buf)
2618 {
2619 guint region_x, region_y, region_width, region_height;
2620 guint window_x, window_y, window_width, window_height;
2621 guint padding_start, padding_end, padding_before, padding_after;
2622 GPtrArray *rendered_blocks =
2623 g_ptr_array_new_with_free_func (
2624 (GDestroyNotify) gst_ttml_render_rendered_image_free);
2625 GstTtmlRenderRenderedImage *region_image = NULL;
2626 GstVideoOverlayComposition *ret = NULL;
2627 guint i;
2628
2629 region_width = (guint) (round (region->style_set->extent_w * render->width));
2630 region_height =
2631 (guint) (round (region->style_set->extent_h * render->height));
2632 region_x = (guint) (round (region->style_set->origin_x * render->width));
2633 region_y = (guint) (round (region->style_set->origin_y * render->height));
2634
2635 padding_start =
2636 (guint) (round (region->style_set->padding_start * render->width));
2637 padding_end =
2638 (guint) (round (region->style_set->padding_end * render->width));
2639 padding_before =
2640 (guint) (round (region->style_set->padding_before * render->height));
2641 padding_after =
2642 (guint) (round (region->style_set->padding_after * render->height));
2643
2644 /* "window" here refers to the section of the region that we're allowed to
2645 * render into, i.e., the region minus padding. */
2646 window_x = region_x + padding_start;
2647 window_y = region_y + padding_before;
2648 window_width = region_width - (padding_start + padding_end);
2649 window_height = region_height - (padding_before + padding_after);
2650
2651 GST_CAT_DEBUG (ttmlrender_debug,
2652 "Padding: start: %u end: %u before: %u after: %u",
2653 padding_start, padding_end, padding_before, padding_after);
2654
2655 /* Render region background, if non-transparent. */
2656 if (!gst_ttml_render_color_is_transparent (®ion->style_set->
2657 background_color)) {
2658 GstBuffer *bg_rect;
2659
2660 bg_rect = gst_ttml_render_draw_rectangle (region_width, region_height,
2661 region->style_set->background_color);
2662 region_image = gst_ttml_render_rendered_image_new (bg_rect, region_x,
2663 region_y, region_width, region_height);
2664 }
2665
2666 /* Render each block and append to list. */
2667 for (i = 0; i < gst_subtitle_region_get_block_count (region); ++i) {
2668 const GstSubtitleBlock *block;
2669 GstTtmlRenderRenderedImage *rendered_block, *block_bg_image, *tmp;
2670 GstBuffer *block_bg_buf;
2671 gint block_height;
2672
2673 block = gst_subtitle_region_get_block (region, i);
2674 rendered_block = gst_ttml_render_render_text_block (render, block, text_buf,
2675 window_width, TRUE);
2676
2677 if (!rendered_block)
2678 continue;
2679
2680 GST_CAT_LOG (ttmlrender_debug, "rendered_block - x:%d y:%d w:%u h:%u",
2681 rendered_block->x, rendered_block->y, rendered_block->width,
2682 rendered_block->height);
2683
2684 switch (block->style_set->text_align) {
2685 case GST_SUBTITLE_TEXT_ALIGN_CENTER:
2686 rendered_block->x
2687 += (gint) round ((window_width - rendered_block->width) / 2.0);
2688 break;
2689
2690 case GST_SUBTITLE_TEXT_ALIGN_RIGHT:
2691 case GST_SUBTITLE_TEXT_ALIGN_END:
2692 rendered_block->x += (window_width - rendered_block->width);
2693 break;
2694
2695 default:
2696 break;
2697 }
2698
2699 tmp = rendered_block;
2700
2701 block_height = rendered_block->height + (2 * rendered_block->y);
2702 block_bg_buf = gst_ttml_render_draw_rectangle (window_width,
2703 block_height, block->style_set->background_color);
2704 block_bg_image = gst_ttml_render_rendered_image_new (block_bg_buf, 0, 0,
2705 window_width, block_height);
2706 rendered_block = gst_ttml_render_rendered_image_combine (block_bg_image,
2707 rendered_block);
2708 gst_ttml_render_rendered_image_free (tmp);
2709 gst_ttml_render_rendered_image_free (block_bg_image);
2710
2711 rendered_block->y = 0;
2712 g_ptr_array_add (rendered_blocks, rendered_block);
2713 }
2714
2715 if (rendered_blocks->len > 0) {
2716 GstTtmlRenderRenderedImage *blocks_image, *tmp;
2717
2718 blocks_image = gst_ttml_render_stitch_images (rendered_blocks,
2719 GST_TTML_DIRECTION_BLOCK);
2720 blocks_image->x += window_x;
2721
2722 switch (region->style_set->display_align) {
2723 case GST_SUBTITLE_DISPLAY_ALIGN_BEFORE:
2724 blocks_image->y = window_y;
2725 break;
2726 case GST_SUBTITLE_DISPLAY_ALIGN_CENTER:
2727 blocks_image->y = region_y + ((gint) ((region_height + padding_before)
2728 - (padding_after + blocks_image->height))) / 2;
2729 break;
2730 case GST_SUBTITLE_DISPLAY_ALIGN_AFTER:
2731 blocks_image->y = (region_y + region_height)
2732 - (padding_after + blocks_image->height);
2733 break;
2734 }
2735
2736 if ((region->style_set->overflow == GST_SUBTITLE_OVERFLOW_MODE_HIDDEN)
2737 && ((blocks_image->height > window_height)
2738 || (blocks_image->width > window_width))) {
2739 GstTtmlRenderRenderedImage *tmp = blocks_image;
2740 blocks_image = gst_ttml_render_rendered_image_crop (blocks_image,
2741 window_x, window_y, window_width, window_height);
2742 gst_ttml_render_rendered_image_free (tmp);
2743 }
2744
2745 tmp = region_image;
2746 region_image =
2747 gst_ttml_render_rendered_image_combine (region_image, blocks_image);
2748 gst_ttml_render_rendered_image_free (tmp);
2749 gst_ttml_render_rendered_image_free (blocks_image);
2750 }
2751
2752 if (region_image) {
2753 ret = gst_ttml_render_compose_overlay (region_image);
2754 gst_ttml_render_rendered_image_free (region_image);
2755 }
2756
2757 g_ptr_array_unref (rendered_blocks);
2758 return ret;
2759 }
2760
2761
2762 static GstFlowReturn
gst_ttml_render_video_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)2763 gst_ttml_render_video_chain (GstPad * pad, GstObject * parent,
2764 GstBuffer * buffer)
2765 {
2766 GstTtmlRender *render;
2767 GstFlowReturn ret = GST_FLOW_OK;
2768 gboolean in_seg = FALSE;
2769 guint64 start, stop, clip_start = 0, clip_stop = 0;
2770 gchar *text = NULL;
2771
2772 render = GST_TTML_RENDER (parent);
2773
2774 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2775 goto missing_timestamp;
2776
2777 /* ignore buffers that are outside of the current segment */
2778 start = GST_BUFFER_TIMESTAMP (buffer);
2779
2780 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2781 stop = GST_CLOCK_TIME_NONE;
2782 } else {
2783 stop = start + GST_BUFFER_DURATION (buffer);
2784 }
2785
2786 GST_LOG_OBJECT (render, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2787 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &render->segment,
2788 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2789
2790 /* segment_clip() will adjust start unconditionally to segment_start if
2791 * no stop time is provided, so handle this ourselves */
2792 if (stop == GST_CLOCK_TIME_NONE && start < render->segment.start)
2793 goto out_of_segment;
2794
2795 in_seg = gst_segment_clip (&render->segment, GST_FORMAT_TIME, start, stop,
2796 &clip_start, &clip_stop);
2797
2798 if (!in_seg)
2799 goto out_of_segment;
2800
2801 /* if the buffer is only partially in the segment, fix up stamps */
2802 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2803 GST_DEBUG_OBJECT (render, "clipping buffer timestamp/duration to segment");
2804 buffer = gst_buffer_make_writable (buffer);
2805 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2806 if (stop != -1)
2807 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2808 }
2809
2810 /* now, after we've done the clipping, fix up end time if there's no
2811 * duration (we only use those estimated values internally though, we
2812 * don't want to set bogus values on the buffer itself) */
2813 if (stop == -1) {
2814 if (render->info.fps_n && render->info.fps_d) {
2815 GST_DEBUG_OBJECT (render, "estimating duration based on framerate");
2816 stop = start + gst_util_uint64_scale_int (GST_SECOND,
2817 render->info.fps_d, render->info.fps_n);
2818 } else {
2819 GST_LOG_OBJECT (render, "no duration, assuming minimal duration");
2820 stop = start + 1; /* we need to assume some interval */
2821 }
2822 }
2823
2824 gst_object_sync_values (GST_OBJECT (render), GST_BUFFER_TIMESTAMP (buffer));
2825
2826 wait_for_text_buf:
2827
2828 GST_TTML_RENDER_LOCK (render);
2829
2830 if (render->video_flushing)
2831 goto flushing;
2832
2833 if (render->video_eos)
2834 goto have_eos;
2835
2836 /* Text pad not linked; push input video frame */
2837 if (!render->text_linked) {
2838 GST_LOG_OBJECT (render, "Text pad not linked");
2839 GST_TTML_RENDER_UNLOCK (render);
2840 ret = gst_pad_push (render->srcpad, buffer);
2841 goto not_linked;
2842 }
2843
2844 /* Text pad linked, check if we have a text buffer queued */
2845 if (render->text_buffer) {
2846 gboolean pop_text = FALSE, valid_text_time = TRUE;
2847 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2848 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2849 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2850 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2851 GstClockTime vid_running_time, vid_running_time_end;
2852
2853 /* if the text buffer isn't stamped right, pop it off the
2854 * queue and display it for the current video frame only */
2855 if (!GST_BUFFER_TIMESTAMP_IS_VALID (render->text_buffer) ||
2856 !GST_BUFFER_DURATION_IS_VALID (render->text_buffer)) {
2857 GST_WARNING_OBJECT (render,
2858 "Got text buffer with invalid timestamp or duration");
2859 pop_text = TRUE;
2860 valid_text_time = FALSE;
2861 } else {
2862 text_start = GST_BUFFER_TIMESTAMP (render->text_buffer);
2863 text_end = text_start + GST_BUFFER_DURATION (render->text_buffer);
2864 }
2865
2866 vid_running_time =
2867 gst_segment_to_running_time (&render->segment, GST_FORMAT_TIME, start);
2868 vid_running_time_end =
2869 gst_segment_to_running_time (&render->segment, GST_FORMAT_TIME, stop);
2870
2871 /* If timestamp and duration are valid */
2872 if (valid_text_time) {
2873 text_running_time =
2874 gst_segment_to_running_time (&render->text_segment,
2875 GST_FORMAT_TIME, text_start);
2876 text_running_time_end =
2877 gst_segment_to_running_time (&render->text_segment,
2878 GST_FORMAT_TIME, text_end);
2879 }
2880
2881 GST_LOG_OBJECT (render, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2882 GST_TIME_ARGS (text_running_time),
2883 GST_TIME_ARGS (text_running_time_end));
2884 GST_LOG_OBJECT (render, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2885 GST_TIME_ARGS (vid_running_time), GST_TIME_ARGS (vid_running_time_end));
2886
2887 /* Text too old or in the future */
2888 if (valid_text_time && text_running_time_end <= vid_running_time) {
2889 /* text buffer too old, get rid of it and do nothing */
2890 GST_LOG_OBJECT (render, "text buffer too old, popping");
2891 pop_text = FALSE;
2892 gst_ttml_render_pop_text (render);
2893 GST_TTML_RENDER_UNLOCK (render);
2894 goto wait_for_text_buf;
2895 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2896 GST_LOG_OBJECT (render, "text in future, pushing video buf");
2897 GST_TTML_RENDER_UNLOCK (render);
2898 /* Push the video frame */
2899 ret = gst_pad_push (render->srcpad, buffer);
2900 } else {
2901 if (render->need_render) {
2902 GstSubtitleRegion *region = NULL;
2903 GstSubtitleMeta *subtitle_meta = NULL;
2904 guint i;
2905
2906 if (render->compositions) {
2907 g_list_free_full (render->compositions,
2908 (GDestroyNotify) gst_video_overlay_composition_unref);
2909 render->compositions = NULL;
2910 }
2911
2912 subtitle_meta = gst_buffer_get_subtitle_meta (render->text_buffer);
2913 if (!subtitle_meta) {
2914 GST_CAT_WARNING (ttmlrender_debug, "Failed to get subtitle meta.");
2915 } else {
2916 for (i = 0; i < subtitle_meta->regions->len; ++i) {
2917 GstVideoOverlayComposition *composition;
2918 region = g_ptr_array_index (subtitle_meta->regions, i);
2919 composition = gst_ttml_render_render_text_region (render, region,
2920 render->text_buffer);
2921 if (composition) {
2922 render->compositions = g_list_append (render->compositions,
2923 composition);
2924 }
2925 }
2926 }
2927 render->need_render = FALSE;
2928 }
2929
2930 GST_TTML_RENDER_UNLOCK (render);
2931 ret = gst_ttml_render_push_frame (render, buffer);
2932
2933 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2934 GST_LOG_OBJECT (render, "text buffer not needed any longer");
2935 pop_text = TRUE;
2936 }
2937 }
2938 if (pop_text) {
2939 GST_TTML_RENDER_LOCK (render);
2940 gst_ttml_render_pop_text (render);
2941 GST_TTML_RENDER_UNLOCK (render);
2942 }
2943 } else {
2944 gboolean wait_for_text_buf = TRUE;
2945
2946 if (render->text_eos)
2947 wait_for_text_buf = FALSE;
2948
2949 if (!render->wait_text)
2950 wait_for_text_buf = FALSE;
2951
2952 /* Text pad linked, but no text buffer available - what now? */
2953 if (render->text_segment.format == GST_FORMAT_TIME) {
2954 GstClockTime text_start_running_time, text_position_running_time;
2955 GstClockTime vid_running_time;
2956
2957 vid_running_time =
2958 gst_segment_to_running_time (&render->segment, GST_FORMAT_TIME,
2959 GST_BUFFER_TIMESTAMP (buffer));
2960 text_start_running_time =
2961 gst_segment_to_running_time (&render->text_segment,
2962 GST_FORMAT_TIME, render->text_segment.start);
2963 text_position_running_time =
2964 gst_segment_to_running_time (&render->text_segment,
2965 GST_FORMAT_TIME, render->text_segment.position);
2966
2967 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2968 vid_running_time < text_start_running_time) ||
2969 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2970 vid_running_time < text_position_running_time)) {
2971 wait_for_text_buf = FALSE;
2972 }
2973 }
2974
2975 if (wait_for_text_buf) {
2976 GST_DEBUG_OBJECT (render, "no text buffer, need to wait for one");
2977 GST_TTML_RENDER_WAIT (render);
2978 GST_DEBUG_OBJECT (render, "resuming");
2979 GST_TTML_RENDER_UNLOCK (render);
2980 goto wait_for_text_buf;
2981 } else {
2982 GST_TTML_RENDER_UNLOCK (render);
2983 GST_LOG_OBJECT (render, "no need to wait for a text buffer");
2984 ret = gst_pad_push (render->srcpad, buffer);
2985 }
2986 }
2987
2988 not_linked:
2989 g_free (text);
2990
2991 /* Update position */
2992 render->segment.position = clip_start;
2993
2994 return ret;
2995
2996 missing_timestamp:
2997 {
2998 GST_WARNING_OBJECT (render, "buffer without timestamp, discarding");
2999 gst_buffer_unref (buffer);
3000 return GST_FLOW_OK;
3001 }
3002
3003 flushing:
3004 {
3005 GST_TTML_RENDER_UNLOCK (render);
3006 GST_DEBUG_OBJECT (render, "flushing, discarding buffer");
3007 gst_buffer_unref (buffer);
3008 return GST_FLOW_FLUSHING;
3009 }
3010 have_eos:
3011 {
3012 GST_TTML_RENDER_UNLOCK (render);
3013 GST_DEBUG_OBJECT (render, "eos, discarding buffer");
3014 gst_buffer_unref (buffer);
3015 return GST_FLOW_EOS;
3016 }
3017 out_of_segment:
3018 {
3019 GST_DEBUG_OBJECT (render, "buffer out of segment, discarding");
3020 gst_buffer_unref (buffer);
3021 return GST_FLOW_OK;
3022 }
3023 }
3024
3025 static GstStateChangeReturn
gst_ttml_render_change_state(GstElement * element,GstStateChange transition)3026 gst_ttml_render_change_state (GstElement * element, GstStateChange transition)
3027 {
3028 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
3029 GstTtmlRender *render = GST_TTML_RENDER (element);
3030
3031 switch (transition) {
3032 case GST_STATE_CHANGE_PAUSED_TO_READY:
3033 GST_TTML_RENDER_LOCK (render);
3034 render->text_flushing = TRUE;
3035 render->video_flushing = TRUE;
3036 /* pop_text will broadcast on the GCond and thus also make the video
3037 * chain exit if it's waiting for a text buffer */
3038 gst_ttml_render_pop_text (render);
3039 GST_TTML_RENDER_UNLOCK (render);
3040 break;
3041 default:
3042 break;
3043 }
3044
3045 ret = parent_class->change_state (element, transition);
3046 if (ret == GST_STATE_CHANGE_FAILURE)
3047 return ret;
3048
3049 switch (transition) {
3050 case GST_STATE_CHANGE_READY_TO_PAUSED:
3051 GST_TTML_RENDER_LOCK (render);
3052 render->text_flushing = FALSE;
3053 render->video_flushing = FALSE;
3054 render->video_eos = FALSE;
3055 render->text_eos = FALSE;
3056 gst_segment_init (&render->segment, GST_FORMAT_TIME);
3057 gst_segment_init (&render->text_segment, GST_FORMAT_TIME);
3058 GST_TTML_RENDER_UNLOCK (render);
3059 break;
3060 default:
3061 break;
3062 }
3063
3064 return ret;
3065 }
3066
3067 static gboolean
gst_element_ttmlrender_init(GstPlugin * plugin)3068 gst_element_ttmlrender_init (GstPlugin * plugin)
3069 {
3070 guint rank = GST_RANK_NONE;
3071
3072 ttml_element_init (plugin);
3073
3074 GST_DEBUG_CATEGORY_INIT (ttmlrender_debug, "ttmlrender", 0, "TTML renderer");
3075
3076 /* We don't want this autoplugged by default yet for now */
3077 if (g_getenv ("GST_TTML_AUTOPLUG")) {
3078 GST_INFO_OBJECT (plugin, "Registering ttml elements with primary rank.");
3079 rank = GST_RANK_PRIMARY;
3080 }
3081
3082 return gst_element_register (plugin, "ttmlrender", rank,
3083 GST_TYPE_TTML_RENDER);
3084 }
3085