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