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