assrender: fix gap event leak and invalid return value
[platform/upstream/gstreamer.git] / ext / assrender / gstassrender.c
1 /*
2  * Copyright (c) 2008 Benjamin Schmitz <vortex@wolpzone.de>
3  * Copyright (c) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 /**
22  * SECTION:element-assrender
23  *
24  * Renders timestamped SSA/ASS subtitles on top of a video stream.
25  *
26  * <refsect2>
27  * <title>Example launch line</title>
28  * |[
29  * gst-launch -v filesrc location=/path/to/mkv ! matroskademux name=d ! queue ! mp3parse ! mad ! audioconvert ! autoaudiosink  d. ! queue ! ffdec_h264 ! videoconvert ! r.   d. ! queue ! "application/x-ass" ! assrender name=r ! videoconvert ! autovideosink
30  * ]| This pipeline demuxes a Matroska file with h.264 video, MP3 audio and embedded ASS subtitles and renders the subtitles on top of the video.
31  * </refsect2>
32  */
33
34 #ifdef HAVE_CONFIG_H
35 #  include <config.h>
36 #endif
37
38 #include <gst/video/gstvideometa.h>
39
40 #include "gstassrender.h"
41
42 #include <string.h>
43
44 GST_DEBUG_CATEGORY_STATIC (gst_ass_render_debug);
45 GST_DEBUG_CATEGORY_STATIC (gst_ass_render_lib_debug);
46 #define GST_CAT_DEFAULT gst_ass_render_debug
47
48 /* Filter signals and props */
49 enum
50 {
51   LAST_SIGNAL
52 };
53
54 enum
55 {
56   PROP_0,
57   PROP_ENABLE,
58   PROP_EMBEDDEDFONTS,
59   PROP_WAIT_TEXT
60 };
61
62 /* FIXME: video-blend.c doesn't support formats with more than 8 bit per
63  * component (which get unpacked into ARGB64 or AYUV64) yet, such as:
64  *  v210, v216, UYVP, GRAY16_LE, GRAY16_BE */
65 #define FORMATS "{ BGRx, RGBx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, \
66     I420, YV12, AYUV, YUY2, UYVY, v308, Y41B, Y42B, Y444, \
67     NV12, NV21, A420, YUV9, YVU9, IYU1, GRAY8 }"
68
69 #define ASSRENDER_CAPS GST_VIDEO_CAPS_MAKE(FORMATS)
70
71 #define ASSRENDER_ALL_CAPS ASSRENDER_CAPS ";" \
72     GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
73
74 static GstStaticCaps sw_template_caps = GST_STATIC_CAPS (ASSRENDER_CAPS);
75
76 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
77     GST_PAD_SRC,
78     GST_PAD_ALWAYS,
79     GST_STATIC_CAPS (ASSRENDER_ALL_CAPS)
80     );
81
82 static GstStaticPadTemplate video_sink_factory =
83 GST_STATIC_PAD_TEMPLATE ("video_sink",
84     GST_PAD_SINK,
85     GST_PAD_ALWAYS,
86     GST_STATIC_CAPS (ASSRENDER_ALL_CAPS)
87     );
88
89 static GstStaticPadTemplate text_sink_factory =
90     GST_STATIC_PAD_TEMPLATE ("text_sink",
91     GST_PAD_SINK,
92     GST_PAD_ALWAYS,
93     GST_STATIC_CAPS ("application/x-ass; application/x-ssa")
94     );
95
96 #define GST_ASS_RENDER_GET_LOCK(ass) (&GST_ASS_RENDER (ass)->lock)
97 #define GST_ASS_RENDER_GET_COND(ass) (&GST_ASS_RENDER (ass)->cond)
98 #define GST_ASS_RENDER_LOCK(ass)     (g_mutex_lock (GST_ASS_RENDER_GET_LOCK (ass)))
99 #define GST_ASS_RENDER_UNLOCK(ass)   (g_mutex_unlock (GST_ASS_RENDER_GET_LOCK (ass)))
100 #define GST_ASS_RENDER_WAIT(ass)     (g_cond_wait (GST_ASS_RENDER_GET_COND (ass), GST_ASS_RENDER_GET_LOCK (ass)))
101 #define GST_ASS_RENDER_SIGNAL(ass)   (g_cond_signal (GST_ASS_RENDER_GET_COND (ass)))
102 #define GST_ASS_RENDER_BROADCAST(ass)(g_cond_broadcast (GST_ASS_RENDER_GET_COND (ass)))
103
104 static void gst_ass_render_set_property (GObject * object, guint prop_id,
105     const GValue * value, GParamSpec * pspec);
106 static void gst_ass_render_get_property (GObject * object, guint prop_id,
107     GValue * value, GParamSpec * pspec);
108
109 static void gst_ass_render_finalize (GObject * object);
110
111 static GstStateChangeReturn gst_ass_render_change_state (GstElement * element,
112     GstStateChange transition);
113
114 #define gst_ass_render_parent_class parent_class
115 G_DEFINE_TYPE (GstAssRender, gst_ass_render, GST_TYPE_ELEMENT);
116
117 static GstCaps *gst_ass_render_get_videosink_caps (GstPad * pad,
118     GstAssRender * render, GstCaps * filter);
119 static GstCaps *gst_ass_render_get_src_caps (GstPad * pad,
120     GstAssRender * render, GstCaps * filter);
121
122 static gboolean gst_ass_render_setcaps_video (GstPad * pad,
123     GstAssRender * render, GstCaps * caps);
124 static gboolean gst_ass_render_setcaps_text (GstPad * pad,
125     GstAssRender * render, GstCaps * caps);
126
127 static GstFlowReturn gst_ass_render_chain_video (GstPad * pad,
128     GstObject * parent, GstBuffer * buf);
129 static GstFlowReturn gst_ass_render_chain_text (GstPad * pad,
130     GstObject * parent, GstBuffer * buf);
131
132 static gboolean gst_ass_render_event_video (GstPad * pad, GstObject * parent,
133     GstEvent * event);
134 static gboolean gst_ass_render_event_text (GstPad * pad, GstObject * parent,
135     GstEvent * event);
136 static gboolean gst_ass_render_event_src (GstPad * pad, GstObject * parent,
137     GstEvent * event);
138
139 static gboolean gst_ass_render_query_video (GstPad * pad, GstObject * parent,
140     GstQuery * query);
141 static gboolean gst_ass_render_query_src (GstPad * pad, GstObject * parent,
142     GstQuery * query);
143
144 /* initialize the plugin's class */
145 static void
146 gst_ass_render_class_init (GstAssRenderClass * klass)
147 {
148   GObjectClass *gobject_class = (GObjectClass *) klass;
149   GstElementClass *gstelement_class = (GstElementClass *) klass;
150
151   gobject_class->set_property = gst_ass_render_set_property;
152   gobject_class->get_property = gst_ass_render_get_property;
153   gobject_class->finalize = gst_ass_render_finalize;
154
155   g_object_class_install_property (gobject_class, PROP_ENABLE,
156       g_param_spec_boolean ("enable", "Enable",
157           "Enable rendering of subtitles", TRUE,
158           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
159
160   g_object_class_install_property (gobject_class, PROP_EMBEDDEDFONTS,
161       g_param_spec_boolean ("embeddedfonts", "Embedded Fonts",
162           "Extract and use fonts embedded in the stream", TRUE,
163           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
164
165   g_object_class_install_property (gobject_class, PROP_WAIT_TEXT,
166       g_param_spec_boolean ("wait-text", "Wait Text",
167           "Whether to wait for subtitles", TRUE,
168           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
169
170   gstelement_class->change_state =
171       GST_DEBUG_FUNCPTR (gst_ass_render_change_state);
172
173   gst_element_class_add_pad_template (gstelement_class,
174       gst_static_pad_template_get (&src_factory));
175   gst_element_class_add_pad_template (gstelement_class,
176       gst_static_pad_template_get (&video_sink_factory));
177   gst_element_class_add_pad_template (gstelement_class,
178       gst_static_pad_template_get (&text_sink_factory));
179
180   gst_element_class_set_static_metadata (gstelement_class, "ASS/SSA Render",
181       "Mixer/Video/Overlay/Subtitle",
182       "Renders ASS/SSA subtitles with libass",
183       "Benjamin Schmitz <vortex@wolpzone.de>, "
184       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
185 }
186
187 #if defined(LIBASS_VERSION) && LIBASS_VERSION >= 0x00907000
188 static void
189 _libass_message_cb (gint level, const gchar * fmt, va_list args,
190     gpointer render)
191 {
192   gchar *message = g_strdup_vprintf (fmt, args);
193
194   if (level < 2)
195     GST_CAT_ERROR_OBJECT (gst_ass_render_lib_debug, render, "%s", message);
196   else if (level < 4)
197     GST_CAT_WARNING_OBJECT (gst_ass_render_lib_debug, render, "%s", message);
198   else if (level < 5)
199     GST_CAT_INFO_OBJECT (gst_ass_render_lib_debug, render, "%s", message);
200   else if (level < 6)
201     GST_CAT_DEBUG_OBJECT (gst_ass_render_lib_debug, render, "%s", message);
202   else
203     GST_CAT_LOG_OBJECT (gst_ass_render_lib_debug, render, "%s", message);
204
205   g_free (message);
206 }
207 #endif
208
209 static void
210 gst_ass_render_init (GstAssRender * render)
211 {
212   GST_DEBUG_OBJECT (render, "init");
213
214   render->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
215   render->video_sinkpad =
216       gst_pad_new_from_static_template (&video_sink_factory, "video_sink");
217   render->text_sinkpad =
218       gst_pad_new_from_static_template (&text_sink_factory, "text_sink");
219
220   gst_pad_set_chain_function (render->video_sinkpad,
221       GST_DEBUG_FUNCPTR (gst_ass_render_chain_video));
222   gst_pad_set_chain_function (render->text_sinkpad,
223       GST_DEBUG_FUNCPTR (gst_ass_render_chain_text));
224
225   gst_pad_set_event_function (render->video_sinkpad,
226       GST_DEBUG_FUNCPTR (gst_ass_render_event_video));
227   gst_pad_set_event_function (render->text_sinkpad,
228       GST_DEBUG_FUNCPTR (gst_ass_render_event_text));
229   gst_pad_set_event_function (render->srcpad,
230       GST_DEBUG_FUNCPTR (gst_ass_render_event_src));
231
232   gst_pad_set_query_function (render->srcpad,
233       GST_DEBUG_FUNCPTR (gst_ass_render_query_src));
234   gst_pad_set_query_function (render->video_sinkpad,
235       GST_DEBUG_FUNCPTR (gst_ass_render_query_video));
236
237   GST_PAD_SET_PROXY_ALLOCATION (render->video_sinkpad);
238
239   gst_element_add_pad (GST_ELEMENT (render), render->srcpad);
240   gst_element_add_pad (GST_ELEMENT (render), render->video_sinkpad);
241   gst_element_add_pad (GST_ELEMENT (render), render->text_sinkpad);
242
243   gst_video_info_init (&render->info);
244
245   g_mutex_init (&render->lock);
246   g_cond_init (&render->cond);
247
248   render->renderer_init_ok = FALSE;
249   render->track_init_ok = FALSE;
250   render->enable = TRUE;
251   render->embeddedfonts = TRUE;
252   render->wait_text = FALSE;
253
254   gst_segment_init (&render->video_segment, GST_FORMAT_TIME);
255   gst_segment_init (&render->subtitle_segment, GST_FORMAT_TIME);
256
257   g_mutex_init (&render->ass_mutex);
258   render->ass_library = ass_library_init ();
259 #if defined(LIBASS_VERSION) && LIBASS_VERSION >= 0x00907000
260   ass_set_message_cb (render->ass_library, _libass_message_cb, render);
261 #endif
262   ass_set_extract_fonts (render->ass_library, 1);
263
264   render->ass_renderer = ass_renderer_init (render->ass_library);
265   if (!render->ass_renderer) {
266     GST_WARNING_OBJECT (render, "cannot create renderer instance");
267     g_assert_not_reached ();
268   }
269
270   render->ass_track = NULL;
271
272   GST_DEBUG_OBJECT (render, "init complete");
273 }
274
275 static void
276 gst_ass_render_finalize (GObject * object)
277 {
278   GstAssRender *render = GST_ASS_RENDER (object);
279
280   g_mutex_clear (&render->lock);
281   g_cond_clear (&render->cond);
282
283   if (render->ass_track) {
284     ass_free_track (render->ass_track);
285   }
286
287   if (render->ass_renderer) {
288     ass_renderer_done (render->ass_renderer);
289   }
290
291   if (render->ass_library) {
292     ass_library_done (render->ass_library);
293   }
294
295   g_mutex_clear (&render->ass_mutex);
296
297   G_OBJECT_CLASS (parent_class)->finalize (object);
298 }
299
300 static void
301 gst_ass_render_set_property (GObject * object, guint prop_id,
302     const GValue * value, GParamSpec * pspec)
303 {
304   GstAssRender *render = GST_ASS_RENDER (object);
305
306   GST_ASS_RENDER_LOCK (render);
307   switch (prop_id) {
308     case PROP_ENABLE:
309       render->enable = g_value_get_boolean (value);
310       break;
311     case PROP_EMBEDDEDFONTS:
312       render->embeddedfonts = g_value_get_boolean (value);
313       g_mutex_lock (&render->ass_mutex);
314       ass_set_extract_fonts (render->ass_library, render->embeddedfonts);
315       g_mutex_unlock (&render->ass_mutex);
316       break;
317     case PROP_WAIT_TEXT:
318       render->wait_text = g_value_get_boolean (value);
319       break;
320     default:
321       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
322       break;
323   }
324   GST_ASS_RENDER_UNLOCK (render);
325 }
326
327 static void
328 gst_ass_render_get_property (GObject * object, guint prop_id,
329     GValue * value, GParamSpec * pspec)
330 {
331   GstAssRender *render = GST_ASS_RENDER (object);
332
333   GST_ASS_RENDER_LOCK (render);
334   switch (prop_id) {
335     case PROP_ENABLE:
336       g_value_set_boolean (value, render->enable);
337       break;
338     case PROP_EMBEDDEDFONTS:
339       g_value_set_boolean (value, render->embeddedfonts);
340       break;
341     case PROP_WAIT_TEXT:
342       g_value_set_boolean (value, render->wait_text);
343       break;
344     default:
345       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
346       break;
347   }
348   GST_ASS_RENDER_UNLOCK (render);
349 }
350
351 /* Called with lock held */
352 static void
353 gst_ass_render_pop_text (GstAssRender * render)
354 {
355   if (render->subtitle_pending) {
356     GST_DEBUG_OBJECT (render, "releasing text buffer %p",
357         render->subtitle_pending);
358     gst_buffer_unref (render->subtitle_pending);
359     render->subtitle_pending = NULL;
360   }
361
362   /* Let the text task know we used that buffer */
363   GST_ASS_RENDER_BROADCAST (render);
364 }
365
366 static GstStateChangeReturn
367 gst_ass_render_change_state (GstElement * element, GstStateChange transition)
368 {
369   GstAssRender *render = GST_ASS_RENDER (element);
370   GstStateChangeReturn ret;
371
372   switch (transition) {
373     case GST_STATE_CHANGE_PAUSED_TO_READY:
374       GST_ASS_RENDER_LOCK (render);
375       render->subtitle_flushing = TRUE;
376       render->video_flushing = TRUE;
377       gst_ass_render_pop_text (render);
378       GST_ASS_RENDER_UNLOCK (render);
379       break;
380     default:
381       break;
382   }
383
384   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
385   if (ret == GST_STATE_CHANGE_FAILURE)
386     return ret;
387
388   switch (transition) {
389     case GST_STATE_CHANGE_PAUSED_TO_READY:
390       g_mutex_lock (&render->ass_mutex);
391       if (render->ass_track)
392         ass_free_track (render->ass_track);
393       render->ass_track = NULL;
394       if (render->composition) {
395         gst_video_overlay_composition_unref (render->composition);
396         render->composition = NULL;
397       }
398       render->track_init_ok = FALSE;
399       render->renderer_init_ok = FALSE;
400       g_mutex_unlock (&render->ass_mutex);
401       break;
402     case GST_STATE_CHANGE_READY_TO_PAUSED:
403       GST_ASS_RENDER_LOCK (render);
404       render->subtitle_flushing = FALSE;
405       render->video_flushing = FALSE;
406       render->video_eos = FALSE;
407       render->subtitle_eos = FALSE;
408       gst_segment_init (&render->video_segment, GST_FORMAT_TIME);
409       gst_segment_init (&render->subtitle_segment, GST_FORMAT_TIME);
410       GST_ASS_RENDER_UNLOCK (render);
411       break;
412     default:
413       break;
414   }
415
416
417   return ret;
418 }
419
420 static gboolean
421 gst_ass_render_query_src (GstPad * pad, GstObject * parent, GstQuery * query)
422 {
423   gboolean res = FALSE;
424
425   switch (GST_QUERY_TYPE (query)) {
426     case GST_QUERY_CAPS:
427     {
428       GstCaps *filter, *caps;
429
430       gst_query_parse_caps (query, &filter);
431       caps = gst_ass_render_get_src_caps (pad, (GstAssRender *) parent, filter);
432       gst_query_set_caps_result (query, caps);
433       gst_caps_unref (caps);
434       res = TRUE;
435       break;
436     }
437     default:
438       res = gst_pad_query_default (pad, parent, query);
439       break;
440   }
441
442   return res;
443 }
444
445 static gboolean
446 gst_ass_render_event_src (GstPad * pad, GstObject * parent, GstEvent * event)
447 {
448   GstAssRender *render = GST_ASS_RENDER (parent);
449   gboolean ret = FALSE;
450
451   GST_DEBUG_OBJECT (render, "received src event %" GST_PTR_FORMAT, event);
452
453   switch (GST_EVENT_TYPE (event)) {
454     case GST_EVENT_SEEK:{
455       GstSeekFlags flags;
456
457       if (!render->track_init_ok) {
458         GST_DEBUG_OBJECT (render, "seek received, pushing upstream");
459         ret = gst_pad_push_event (render->video_sinkpad, event);
460         return ret;
461       }
462
463       GST_DEBUG_OBJECT (render, "seek received, driving from here");
464
465       gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
466
467       /* Flush downstream, only for flushing seek */
468       if (flags & GST_SEEK_FLAG_FLUSH)
469         gst_pad_push_event (render->srcpad, gst_event_new_flush_start ());
470
471       /* Mark subtitle as flushing, unblocks chains */
472       GST_ASS_RENDER_LOCK (render);
473       render->subtitle_flushing = TRUE;
474       render->video_flushing = TRUE;
475       gst_ass_render_pop_text (render);
476       GST_ASS_RENDER_UNLOCK (render);
477
478       /* Seek on each sink pad */
479       gst_event_ref (event);
480       ret = gst_pad_push_event (render->video_sinkpad, event);
481       if (ret) {
482         ret = gst_pad_push_event (render->text_sinkpad, event);
483       } else {
484         gst_event_unref (event);
485       }
486       break;
487     }
488     default:
489       if (render->track_init_ok) {
490         gst_event_ref (event);
491         ret = gst_pad_push_event (render->video_sinkpad, event);
492         gst_pad_push_event (render->text_sinkpad, event);
493       } else {
494         ret = gst_pad_push_event (render->video_sinkpad, event);
495       }
496       break;
497   }
498
499   return ret;
500 }
501
502 /**
503  * gst_ass_render_add_feature_and_intersect:
504  *
505  * Creates a new #GstCaps containing the (given caps +
506  * given caps feature) + (given caps intersected by the
507  * given filter).
508  *
509  * Returns: the new #GstCaps
510  */
511 static GstCaps *
512 gst_ass_render_add_feature_and_intersect (GstCaps * caps,
513     const gchar * feature, GstCaps * filter)
514 {
515   int i, caps_size;
516   GstCaps *new_caps;
517
518   new_caps = gst_caps_copy (caps);
519
520   caps_size = gst_caps_get_size (new_caps);
521   for (i = 0; i < caps_size; i++) {
522     GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
523     if (!gst_caps_features_is_any (features)) {
524       gst_caps_features_add (features, feature);
525     }
526   }
527
528   gst_caps_append (new_caps, gst_caps_intersect_full (caps,
529           filter, GST_CAPS_INTERSECT_FIRST));
530
531   return new_caps;
532 }
533
534 /**
535  * gst_ass_render_intersect_by_feature:
536  *
537  * Creates a new #GstCaps based on the following filtering rule.
538  *
539  * For each individual caps contained in given caps, if the
540  * caps uses the given caps feature, keep a version of the caps
541  * with the feature and an another one without. Otherwise, intersect
542  * the caps with the given filter.
543  *
544  * Returns: the new #GstCaps
545  */
546 static GstCaps *
547 gst_ass_render_intersect_by_feature (GstCaps * caps,
548     const gchar * feature, GstCaps * filter)
549 {
550   int i, caps_size;
551   GstCaps *new_caps;
552
553   new_caps = gst_caps_new_empty ();
554
555   caps_size = gst_caps_get_size (caps);
556   for (i = 0; i < caps_size; i++) {
557     GstStructure *caps_structure = gst_caps_get_structure (caps, i);
558     GstCapsFeatures *caps_features =
559         gst_caps_features_copy (gst_caps_get_features (caps, i));
560     GstCaps *filtered_caps;
561     GstCaps *simple_caps =
562         gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
563     gst_caps_set_features (simple_caps, 0, caps_features);
564
565     if (gst_caps_features_contains (caps_features, feature)) {
566       gst_caps_append (new_caps, gst_caps_copy (simple_caps));
567
568       gst_caps_features_remove (caps_features, feature);
569       filtered_caps = gst_caps_ref (simple_caps);
570     } else {
571       filtered_caps = gst_caps_intersect_full (simple_caps, filter,
572           GST_CAPS_INTERSECT_FIRST);
573     }
574
575     gst_caps_unref (simple_caps);
576     gst_caps_append (new_caps, filtered_caps);
577   }
578
579   return new_caps;
580 }
581
582 static GstCaps *
583 gst_ass_render_get_videosink_caps (GstPad * pad, GstAssRender * render,
584     GstCaps * filter)
585 {
586   GstPad *srcpad = render->srcpad;
587   GstCaps *peer_caps = NULL, *caps = NULL, *assrender_filter = NULL;
588
589   if (filter) {
590     /* filter caps + composition feature + filter caps
591      * filtered by the software caps. */
592     GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
593     assrender_filter = gst_ass_render_add_feature_and_intersect (filter,
594         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
595     gst_caps_unref (sw_caps);
596
597     GST_DEBUG_OBJECT (render, "assrender filter %" GST_PTR_FORMAT,
598         assrender_filter);
599   }
600
601   peer_caps = gst_pad_peer_query_caps (srcpad, assrender_filter);
602
603   if (assrender_filter)
604     gst_caps_unref (assrender_filter);
605
606   if (peer_caps) {
607
608     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);
609
610     if (gst_caps_is_any (peer_caps)) {
611
612       /* if peer returns ANY caps, return filtered src pad template caps */
613       caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
614     } else {
615
616       /* duplicate caps which contains the composition into one version with
617        * the meta and one without. Filter the other caps by the software caps */
618       GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
619       caps = gst_ass_render_intersect_by_feature (peer_caps,
620           GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
621       gst_caps_unref (sw_caps);
622     }
623
624     gst_caps_unref (peer_caps);
625
626   } else {
627     /* no peer, our padtemplate is enough then */
628     caps = gst_pad_get_pad_template_caps (pad);
629   }
630
631   if (filter) {
632     GstCaps *intersection = gst_caps_intersect_full (filter, caps,
633         GST_CAPS_INTERSECT_FIRST);
634     gst_caps_unref (caps);
635     caps = intersection;
636   }
637
638   GST_DEBUG_OBJECT (render, "returning  %" GST_PTR_FORMAT, caps);
639
640   return caps;
641 }
642
643 static GstCaps *
644 gst_ass_render_get_src_caps (GstPad * pad, GstAssRender * render,
645     GstCaps * filter)
646 {
647   GstPad *sinkpad = render->video_sinkpad;
648   GstCaps *peer_caps = NULL, *caps = NULL, *assrender_filter = NULL;
649
650   if (filter) {
651     /* duplicate filter caps which contains the composition into one version
652      * with the meta and one without. Filter the other caps by the software
653      * caps */
654     GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
655     assrender_filter =
656         gst_ass_render_intersect_by_feature (filter,
657         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
658     gst_caps_unref (sw_caps);
659   }
660
661   peer_caps = gst_pad_peer_query_caps (sinkpad, assrender_filter);
662
663   if (assrender_filter)
664     gst_caps_unref (assrender_filter);
665
666   if (peer_caps) {
667
668     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);
669
670     if (gst_caps_is_any (peer_caps)) {
671
672       /* if peer returns ANY caps, return filtered sink pad template caps */
673       caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad));
674
675     } else {
676
677       /* return upstream caps + composition feature + upstream caps
678        * filtered by the software caps. */
679       GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
680       caps = gst_ass_render_add_feature_and_intersect (peer_caps,
681           GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
682       gst_caps_unref (sw_caps);
683     }
684
685     gst_caps_unref (peer_caps);
686
687   } else {
688     /* no peer, our padtemplate is enough then */
689     caps = gst_pad_get_pad_template_caps (pad);
690   }
691
692   if (filter) {
693     GstCaps *intersection;
694
695     intersection =
696         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
697     gst_caps_unref (caps);
698     caps = intersection;
699   }
700
701   GST_DEBUG_OBJECT (render, "returning  %" GST_PTR_FORMAT, caps);
702
703   return caps;
704 }
705
706 static void
707 blit_bgra_premultiplied (GstAssRender * render, ASS_Image * ass_image,
708     guint8 * data, gint width, gint height, gint stride, gint x_off, gint y_off)
709 {
710   guint counter = 0;
711   gint alpha, r, g, b, k;
712   const guint8 *src;
713   guint8 *dst;
714   gint x, y, w, h;
715   gint dst_skip;
716   gint src_skip;
717   gint dst_x, dst_y;
718
719   memset (data, 0, stride * height);
720
721   while (ass_image) {
722     dst_x = ass_image->dst_x + x_off;
723     dst_y = ass_image->dst_y + y_off;
724
725     if (dst_y >= height || dst_x >= width)
726       goto next;
727
728     alpha = 255 - (ass_image->color & 0xff);
729     r = ((ass_image->color) >> 24) & 0xff;
730     g = ((ass_image->color) >> 16) & 0xff;
731     b = ((ass_image->color) >> 8) & 0xff;
732     src = ass_image->bitmap;
733     dst = data + dst_y * stride + dst_x * 4;
734
735     w = MIN (ass_image->w, width - dst_x);
736     h = MIN (ass_image->h, height - dst_y);
737     src_skip = ass_image->stride - w;
738     dst_skip = stride - w * 4;
739
740     for (y = 0; y < h; y++) {
741       for (x = 0; x < w; x++) {
742         k = src[0] * alpha / 255;
743         if (dst[3] == 0) {
744           dst[3] = k;
745           dst[2] = (k * r) / 255;
746           dst[1] = (k * g) / 255;
747           dst[0] = (k * b) / 255;
748         } else {
749           dst[3] = k + (255 - k) * dst[3] / 255;
750           dst[2] = (k * r + (255 - k) * dst[2]) / 255;
751           dst[1] = (k * g + (255 - k) * dst[1]) / 255;
752           dst[0] = (k * b + (255 - k) * dst[0]) / 255;
753         }
754         src++;
755         dst += 4;
756       }
757       src += src_skip;
758       dst += dst_skip;
759     }
760   next:
761     counter++;
762     ass_image = ass_image->next;
763   }
764   GST_LOG_OBJECT (render, "amount of rendered ass_image: %u", counter);
765 }
766
767 static gboolean
768 gst_ass_render_can_handle_caps (GstCaps * incaps)
769 {
770   static GstStaticCaps static_caps = GST_STATIC_CAPS (ASSRENDER_CAPS);
771   gboolean ret;
772   GstCaps *caps;
773
774   caps = gst_static_caps_get (&static_caps);
775   ret = gst_caps_is_subset (incaps, caps);
776   gst_caps_unref (caps);
777
778   return ret;
779 }
780
781 static gboolean
782 gst_ass_render_setcaps_video (GstPad * pad, GstAssRender * render,
783     GstCaps * caps)
784 {
785   GstQuery *query;
786   gboolean ret = FALSE;
787   gint par_n = 1, par_d = 1;
788   gdouble dar;
789   GstVideoInfo info;
790   gboolean attach = FALSE;
791   gboolean caps_has_meta = TRUE;
792   GstCapsFeatures *f;
793   GstCaps *original_caps = caps;
794
795   if (!gst_video_info_from_caps (&info, caps))
796     goto invalid_caps;
797
798   render->info = info;
799   gst_caps_ref (caps);
800
801   /* Try to use the overlay meta if possible */
802   f = gst_caps_get_features (caps, 0);
803
804   /* if the caps doesn't have the overlay meta, we query if downstream
805    * accepts it before trying the version without the meta
806    * If upstream already is using the meta then we can only use it */
807   if (!f
808       || !gst_caps_features_contains (f,
809           GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION)) {
810     GstCaps *overlay_caps;
811
812     /* In this case we added the meta, but we can work without it
813      * so preserve the original caps so we can use it as a fallback */
814     overlay_caps = gst_caps_copy (caps);
815
816     f = gst_caps_get_features (overlay_caps, 0);
817     gst_caps_features_add (f,
818         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
819
820     ret = gst_pad_peer_query_accept_caps (render->srcpad, overlay_caps);
821     GST_DEBUG_OBJECT (render, "Downstream accepts the overlay meta: %d", ret);
822     if (ret) {
823       gst_caps_unref (caps);
824       caps = overlay_caps;
825
826     } else {
827       /* fallback to the original */
828       gst_caps_unref (overlay_caps);
829       caps_has_meta = FALSE;
830     }
831
832   }
833   GST_DEBUG_OBJECT (render, "Using caps %" GST_PTR_FORMAT, caps);
834   ret = gst_pad_set_caps (render->srcpad, caps);
835   gst_caps_unref (caps);
836
837   if (!ret)
838     goto out;
839
840   render->width = info.width;
841   render->height = info.height;
842
843   query = gst_query_new_allocation (caps, FALSE);
844   if (caps_has_meta && gst_pad_peer_query (render->srcpad, query)) {
845     if (gst_query_find_allocation_meta (query,
846             GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
847       attach = TRUE;
848   }
849   gst_query_unref (query);
850
851   render->attach_compo_to_buffer = attach;
852
853   if (!attach) {
854     if (caps_has_meta) {
855       /* Some elements (fakesink) claim to accept the meta on caps but won't
856          put it in the allocation query result, this leads below
857          check to fail. Prevent this by removing the meta from caps */
858       caps = original_caps;
859       ret = gst_pad_set_caps (render->srcpad, caps);
860       if (!ret)
861         goto out;
862     }
863     if (!gst_ass_render_can_handle_caps (caps))
864       goto unsupported_caps;
865   }
866
867   g_mutex_lock (&render->ass_mutex);
868   ass_set_frame_size (render->ass_renderer, render->width, render->height);
869
870   dar = (((gdouble) par_n) * ((gdouble) render->width))
871       / (((gdouble) par_d) * ((gdouble) render->height));
872 #if !defined(LIBASS_VERSION) || LIBASS_VERSION < 0x00907000
873   ass_set_aspect_ratio (render->ass_renderer, dar);
874 #else
875   ass_set_aspect_ratio (render->ass_renderer,
876       dar, ((gdouble) render->width) / ((gdouble) render->height));
877 #endif
878   ass_set_font_scale (render->ass_renderer, 1.0);
879   ass_set_hinting (render->ass_renderer, ASS_HINTING_LIGHT);
880
881 #if !defined(LIBASS_VERSION) || LIBASS_VERSION < 0x00907000
882   ass_set_fonts (render->ass_renderer, "Arial", "sans-serif");
883   ass_set_fonts (render->ass_renderer, NULL, "Sans");
884 #else
885   ass_set_fonts (render->ass_renderer, "Arial", "sans-serif", 1, NULL, 1);
886   ass_set_fonts (render->ass_renderer, NULL, "Sans", 1, NULL, 1);
887 #endif
888   ass_set_margins (render->ass_renderer, 0, 0, 0, 0);
889   ass_set_use_margins (render->ass_renderer, 0);
890   g_mutex_unlock (&render->ass_mutex);
891
892   render->renderer_init_ok = TRUE;
893
894   GST_DEBUG_OBJECT (render, "ass renderer setup complete");
895
896 out:
897
898   return ret;
899
900   /* ERRORS */
901 invalid_caps:
902   {
903     GST_ERROR_OBJECT (render, "Can't parse caps: %" GST_PTR_FORMAT, caps);
904     ret = FALSE;
905     goto out;
906   }
907 unsupported_caps:
908   {
909     GST_ERROR_OBJECT (render, "Unsupported caps: %" GST_PTR_FORMAT, caps);
910     ret = FALSE;
911     goto out;
912   }
913 }
914
915 static gboolean
916 gst_ass_render_setcaps_text (GstPad * pad, GstAssRender * render,
917     GstCaps * caps)
918 {
919   GstStructure *structure;
920   const GValue *value;
921   GstBuffer *priv;
922   GstMapInfo map;
923   gboolean ret = FALSE;
924
925   structure = gst_caps_get_structure (caps, 0);
926
927   GST_DEBUG_OBJECT (render, "text pad linked with caps:  %" GST_PTR_FORMAT,
928       caps);
929
930   value = gst_structure_get_value (structure, "codec_data");
931
932   g_mutex_lock (&render->ass_mutex);
933   if (value != NULL) {
934     priv = gst_value_get_buffer (value);
935     g_return_val_if_fail (priv != NULL, FALSE);
936
937     gst_buffer_map (priv, &map, GST_MAP_READ);
938
939     if (!render->ass_track)
940       render->ass_track = ass_new_track (render->ass_library);
941
942     ass_process_codec_private (render->ass_track, (char *) map.data, map.size);
943
944     gst_buffer_unmap (priv, &map);
945
946     GST_DEBUG_OBJECT (render, "ass track created");
947
948     render->track_init_ok = TRUE;
949
950     ret = TRUE;
951   } else if (!render->ass_track) {
952     render->ass_track = ass_new_track (render->ass_library);
953
954     render->track_init_ok = TRUE;
955
956     ret = TRUE;
957   }
958   g_mutex_unlock (&render->ass_mutex);
959
960   return ret;
961 }
962
963
964 static void
965 gst_ass_render_process_text (GstAssRender * render, GstBuffer * buffer,
966     GstClockTime running_time, GstClockTime duration)
967 {
968   GstMapInfo map;
969   gdouble pts_start, pts_end;
970
971   pts_start = running_time;
972   pts_start /= GST_MSECOND;
973   pts_end = duration;
974   pts_end /= GST_MSECOND;
975
976   GST_DEBUG_OBJECT (render,
977       "Processing subtitles with running time %" GST_TIME_FORMAT
978       " and duration %" GST_TIME_FORMAT, GST_TIME_ARGS (running_time),
979       GST_TIME_ARGS (duration));
980
981   gst_buffer_map (buffer, &map, GST_MAP_READ);
982
983   g_mutex_lock (&render->ass_mutex);
984   ass_process_chunk (render->ass_track, (gchar *) map.data, map.size,
985       pts_start, pts_end);
986   g_mutex_unlock (&render->ass_mutex);
987
988   gst_buffer_unmap (buffer, &map);
989 }
990
991 static GstVideoOverlayComposition *
992 gst_ass_render_composite_overlay (GstAssRender * render, ASS_Image * images)
993 {
994   GstVideoOverlayComposition *composition;
995   GstVideoOverlayRectangle *rectangle;
996   GstVideoMeta *vmeta;
997   GstMapInfo map;
998   GstBuffer *buffer;
999   ASS_Image *image;
1000   gint min_x, min_y;
1001   gint max_x, max_y;
1002   gint width, height;
1003   gint stride;
1004   gpointer data;
1005
1006   min_x = G_MAXINT;
1007   min_y = G_MAXINT;
1008   max_x = 0;
1009   max_y = 0;
1010
1011   /* find bounding box of all images, to limit the overlay rectangle size */
1012   for (image = images; image; image = image->next) {
1013     if (min_x > image->dst_x)
1014       min_x = image->dst_x;
1015     if (min_y > image->dst_y)
1016       min_y = image->dst_y;
1017     if (max_x < image->dst_x + image->w)
1018       max_x = image->dst_x + image->w;
1019     if (max_y < image->dst_y + image->h)
1020       max_y = image->dst_y + image->h;
1021   }
1022
1023   width = MIN (max_x - min_x, render->width);
1024   height = MIN (max_y - min_y, render->height);
1025
1026   GST_DEBUG_OBJECT (render, "render overlay rectangle %dx%d%+d%+d",
1027       width, height, min_x, min_y);
1028
1029   buffer = gst_buffer_new_and_alloc (4 * width * height);
1030   if (!buffer) {
1031     GST_ERROR_OBJECT (render, "Failed to allocate overlay buffer");
1032     return NULL;
1033   }
1034
1035   vmeta = gst_buffer_add_video_meta (buffer, GST_VIDEO_FRAME_FLAG_NONE,
1036       GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB, width, height);
1037
1038   if (!gst_video_meta_map (vmeta, 0, &map, &data, &stride, GST_MAP_READWRITE)) {
1039     GST_ERROR_OBJECT (render, "Failed to map overlay buffer");
1040     gst_buffer_unref (buffer);
1041     return NULL;
1042   }
1043
1044   blit_bgra_premultiplied (render, images, data, width, height, stride,
1045       -min_x, -min_y);
1046   gst_video_meta_unmap (vmeta, 0, &map);
1047
1048   rectangle = gst_video_overlay_rectangle_new_raw (buffer, min_x, min_y,
1049       width, height, GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1050
1051   gst_buffer_unref (buffer);
1052
1053   composition = gst_video_overlay_composition_new (rectangle);
1054   gst_video_overlay_rectangle_unref (rectangle);
1055
1056   return composition;
1057 }
1058
1059 static gboolean
1060 gst_ass_render_push_frame (GstAssRender * render, GstBuffer * video_frame)
1061 {
1062   GstVideoFrame frame;
1063
1064   if (!render->composition)
1065     goto done;
1066
1067   video_frame = gst_buffer_make_writable (video_frame);
1068
1069   if (render->attach_compo_to_buffer) {
1070     gst_buffer_add_video_overlay_composition_meta (video_frame,
1071         render->composition);
1072     goto done;
1073   }
1074
1075   if (!gst_video_frame_map (&frame, &render->info, video_frame,
1076           GST_MAP_READWRITE)) {
1077     GST_WARNING_OBJECT (render, "failed to map video frame for blending");
1078     goto done;
1079   }
1080
1081   gst_video_overlay_composition_blend (render->composition, &frame);
1082   gst_video_frame_unmap (&frame);
1083
1084 done:
1085   return gst_pad_push (render->srcpad, video_frame);
1086 }
1087
1088 static GstFlowReturn
1089 gst_ass_render_chain_video (GstPad * pad, GstObject * parent,
1090     GstBuffer * buffer)
1091 {
1092   GstAssRender *render = GST_ASS_RENDER (parent);
1093   GstFlowReturn ret = GST_FLOW_OK;
1094   gboolean in_seg = FALSE;
1095   guint64 start, stop, clip_start = 0, clip_stop = 0;
1096   ASS_Image *ass_image;
1097
1098   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1099     goto missing_timestamp;
1100
1101   /* ignore buffers that are outside of the current segment */
1102   start = GST_BUFFER_TIMESTAMP (buffer);
1103
1104   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
1105     stop = GST_CLOCK_TIME_NONE;
1106   } else {
1107     stop = start + GST_BUFFER_DURATION (buffer);
1108   }
1109
1110   /* segment_clip() will adjust start unconditionally to segment_start if
1111    * no stop time is provided, so handle this ourselves */
1112   if (stop == GST_CLOCK_TIME_NONE && start < render->video_segment.start)
1113     goto out_of_segment;
1114
1115   in_seg =
1116       gst_segment_clip (&render->video_segment, GST_FORMAT_TIME, start, stop,
1117       &clip_start, &clip_stop);
1118
1119   if (!in_seg)
1120     goto out_of_segment;
1121
1122   /* if the buffer is only partially in the segment, fix up stamps */
1123   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
1124     GST_DEBUG_OBJECT (render, "clipping buffer timestamp/duration to segment");
1125     buffer = gst_buffer_make_writable (buffer);
1126     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1127     if (stop != -1)
1128       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1129   }
1130
1131   /* now, after we've done the clipping, fix up end time if there's no
1132    * duration (we only use those estimated values internally though, we
1133    * don't want to set bogus values on the buffer itself) */
1134   if (stop == -1) {
1135     if (render->info.fps_n && render->info.fps_d) {
1136       GST_DEBUG_OBJECT (render, "estimating duration based on framerate");
1137       stop =
1138           start + gst_util_uint64_scale_int (GST_SECOND, render->info.fps_d,
1139           render->info.fps_n);
1140     } else {
1141       GST_WARNING_OBJECT (render, "no duration, assuming minimal duration");
1142       stop = start + 1;         /* we need to assume some interval */
1143     }
1144   }
1145
1146 wait_for_text_buf:
1147
1148   GST_ASS_RENDER_LOCK (render);
1149
1150   if (render->video_flushing)
1151     goto flushing;
1152
1153   if (render->video_eos)
1154     goto have_eos;
1155
1156   if (render->renderer_init_ok && render->track_init_ok && render->enable) {
1157     /* Text pad linked, check if we have a text buffer queued */
1158     if (render->subtitle_pending) {
1159       GstClockTime text_start = GST_CLOCK_TIME_NONE;
1160       GstClockTime text_end = GST_CLOCK_TIME_NONE;
1161       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
1162       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
1163       GstClockTime vid_running_time, vid_running_time_end;
1164       gdouble timestamp;
1165       gint changed = 0;
1166
1167       /* if the text buffer isn't stamped right, pop it off the
1168        * queue and display it for the current video frame only */
1169       if (!GST_BUFFER_TIMESTAMP_IS_VALID (render->subtitle_pending) ||
1170           !GST_BUFFER_DURATION_IS_VALID (render->subtitle_pending)) {
1171         GST_WARNING_OBJECT (render,
1172             "Got text buffer with invalid timestamp or duration");
1173         gst_ass_render_pop_text (render);
1174         GST_ASS_RENDER_UNLOCK (render);
1175         goto wait_for_text_buf;
1176       }
1177
1178       text_start = GST_BUFFER_TIMESTAMP (render->subtitle_pending);
1179       text_end = text_start + GST_BUFFER_DURATION (render->subtitle_pending);
1180
1181       vid_running_time =
1182           gst_segment_to_running_time (&render->video_segment, GST_FORMAT_TIME,
1183           start);
1184       vid_running_time_end =
1185           gst_segment_to_running_time (&render->video_segment, GST_FORMAT_TIME,
1186           stop);
1187
1188       /* If timestamp and duration are valid */
1189       text_running_time =
1190           gst_segment_to_running_time (&render->video_segment,
1191           GST_FORMAT_TIME, text_start);
1192       text_running_time_end =
1193           gst_segment_to_running_time (&render->video_segment,
1194           GST_FORMAT_TIME, text_end);
1195
1196       GST_LOG_OBJECT (render, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
1197           GST_TIME_ARGS (text_running_time),
1198           GST_TIME_ARGS (text_running_time_end));
1199       GST_LOG_OBJECT (render, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
1200           GST_TIME_ARGS (vid_running_time),
1201           GST_TIME_ARGS (vid_running_time_end));
1202
1203       /* Text too old */
1204       if (text_running_time_end <= vid_running_time) {
1205         GST_DEBUG_OBJECT (render, "text buffer too old, popping");
1206         gst_ass_render_pop_text (render);
1207         GST_ASS_RENDER_UNLOCK (render);
1208         goto wait_for_text_buf;
1209       }
1210
1211       if (render->need_process) {
1212         GST_DEBUG_OBJECT (render, "process text buffer");
1213         gst_ass_render_process_text (render, render->subtitle_pending,
1214             text_running_time, text_running_time_end - text_running_time);
1215         render->need_process = FALSE;
1216       }
1217
1218       GST_ASS_RENDER_UNLOCK (render);
1219
1220       /* libass needs timestamps in ms */
1221       timestamp = vid_running_time / GST_MSECOND;
1222
1223       g_mutex_lock (&render->ass_mutex);
1224       ass_image = ass_render_frame (render->ass_renderer, render->ass_track,
1225           timestamp, &changed);
1226       g_mutex_unlock (&render->ass_mutex);
1227
1228       if ((!ass_image || changed) && render->composition) {
1229         GST_DEBUG_OBJECT (render, "release overlay (changed %d)", changed);
1230         gst_video_overlay_composition_unref (render->composition);
1231         render->composition = NULL;
1232       }
1233
1234       if (ass_image != NULL) {
1235         if (!render->composition)
1236           render->composition = gst_ass_render_composite_overlay (render,
1237               ass_image);
1238       } else {
1239         GST_DEBUG_OBJECT (render, "nothing to render right now");
1240       }
1241
1242       /* Push the video frame */
1243       ret = gst_ass_render_push_frame (render, buffer);
1244
1245       if (text_running_time_end <= vid_running_time_end) {
1246         GST_ASS_RENDER_LOCK (render);
1247         gst_ass_render_pop_text (render);
1248         GST_ASS_RENDER_UNLOCK (render);
1249       }
1250     } else {
1251       gboolean wait_for_text_buf = TRUE;
1252
1253       if (render->subtitle_eos)
1254         wait_for_text_buf = FALSE;
1255
1256       if (!render->wait_text)
1257         wait_for_text_buf = FALSE;
1258
1259       /* Text pad linked, but no text buffer available - what now? */
1260       if (render->subtitle_segment.format == GST_FORMAT_TIME) {
1261         GstClockTime text_start_running_time, text_last_stop_running_time;
1262         GstClockTime vid_running_time;
1263
1264         vid_running_time =
1265             gst_segment_to_running_time (&render->video_segment,
1266             GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (buffer));
1267         text_start_running_time =
1268             gst_segment_to_running_time (&render->subtitle_segment,
1269             GST_FORMAT_TIME, render->subtitle_segment.start);
1270         text_last_stop_running_time =
1271             gst_segment_to_running_time (&render->subtitle_segment,
1272             GST_FORMAT_TIME, render->subtitle_segment.position);
1273
1274         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
1275                 vid_running_time < text_start_running_time) ||
1276             (GST_CLOCK_TIME_IS_VALID (text_last_stop_running_time) &&
1277                 vid_running_time < text_last_stop_running_time)) {
1278           wait_for_text_buf = FALSE;
1279         }
1280       }
1281
1282       if (wait_for_text_buf) {
1283         GST_DEBUG_OBJECT (render, "no text buffer, need to wait for one");
1284         GST_ASS_RENDER_WAIT (render);
1285         GST_DEBUG_OBJECT (render, "resuming");
1286         GST_ASS_RENDER_UNLOCK (render);
1287         goto wait_for_text_buf;
1288       } else {
1289         GST_ASS_RENDER_UNLOCK (render);
1290         GST_LOG_OBJECT (render, "no need to wait for a text buffer");
1291         ret = gst_pad_push (render->srcpad, buffer);
1292       }
1293     }
1294   } else {
1295     GST_LOG_OBJECT (render, "rendering disabled, doing buffer passthrough");
1296
1297     GST_ASS_RENDER_UNLOCK (render);
1298     ret = gst_pad_push (render->srcpad, buffer);
1299     return ret;
1300   }
1301
1302   GST_DEBUG_OBJECT (render, "leaving chain for buffer %p ret=%d", buffer, ret);
1303
1304   /* Update last_stop */
1305   render->video_segment.position = clip_start;
1306
1307   return ret;
1308
1309 missing_timestamp:
1310   {
1311     GST_WARNING_OBJECT (render, "buffer without timestamp, discarding");
1312     gst_buffer_unref (buffer);
1313     return GST_FLOW_OK;
1314   }
1315 flushing:
1316   {
1317     GST_ASS_RENDER_UNLOCK (render);
1318     GST_DEBUG_OBJECT (render, "flushing, discarding buffer");
1319     gst_buffer_unref (buffer);
1320     return GST_FLOW_FLUSHING;
1321   }
1322 have_eos:
1323   {
1324     GST_ASS_RENDER_UNLOCK (render);
1325     GST_DEBUG_OBJECT (render, "eos, discarding buffer");
1326     gst_buffer_unref (buffer);
1327     return GST_FLOW_EOS;
1328   }
1329 out_of_segment:
1330   {
1331     GST_DEBUG_OBJECT (render, "buffer out of segment, discarding");
1332     gst_buffer_unref (buffer);
1333     return GST_FLOW_OK;
1334   }
1335 }
1336
1337 static GstFlowReturn
1338 gst_ass_render_chain_text (GstPad * pad, GstObject * parent, GstBuffer * buffer)
1339 {
1340   GstFlowReturn ret = GST_FLOW_OK;
1341   GstAssRender *render = GST_ASS_RENDER (parent);
1342   gboolean in_seg = FALSE;
1343   guint64 clip_start = 0, clip_stop = 0;
1344
1345   GST_DEBUG_OBJECT (render, "entering chain for buffer %p", buffer);
1346
1347   GST_ASS_RENDER_LOCK (render);
1348
1349   if (render->subtitle_flushing) {
1350     GST_ASS_RENDER_UNLOCK (render);
1351     ret = GST_FLOW_FLUSHING;
1352     GST_LOG_OBJECT (render, "text flushing");
1353     goto beach;
1354   }
1355
1356   if (render->subtitle_eos) {
1357     GST_ASS_RENDER_UNLOCK (render);
1358     ret = GST_FLOW_EOS;
1359     GST_LOG_OBJECT (render, "text EOS");
1360     goto beach;
1361   }
1362
1363   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1364     GstClockTime stop;
1365
1366     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1367       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1368     else
1369       stop = GST_CLOCK_TIME_NONE;
1370
1371     in_seg = gst_segment_clip (&render->subtitle_segment, GST_FORMAT_TIME,
1372         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1373   } else {
1374     in_seg = TRUE;
1375   }
1376
1377   if (in_seg) {
1378     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1379       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1380     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
1381       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1382
1383     if (render->subtitle_pending
1384         && (!GST_BUFFER_TIMESTAMP_IS_VALID (render->subtitle_pending)
1385             || !GST_BUFFER_DURATION_IS_VALID (render->subtitle_pending))) {
1386       gst_buffer_unref (render->subtitle_pending);
1387       render->subtitle_pending = NULL;
1388       GST_ASS_RENDER_BROADCAST (render);
1389     } else {
1390       /* Wait for the previous buffer to go away */
1391       while (render->subtitle_pending != NULL) {
1392         GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
1393             GST_DEBUG_PAD_NAME (pad));
1394         GST_ASS_RENDER_WAIT (render);
1395         GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
1396         if (render->subtitle_flushing) {
1397           GST_ASS_RENDER_UNLOCK (render);
1398           ret = GST_FLOW_FLUSHING;
1399           goto beach;
1400         }
1401       }
1402     }
1403
1404     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1405       render->subtitle_segment.position = clip_start;
1406
1407     GST_DEBUG_OBJECT (render,
1408         "New buffer arrived for timestamp %" GST_TIME_FORMAT,
1409         GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)));
1410     render->subtitle_pending = gst_buffer_ref (buffer);
1411     render->need_process = TRUE;
1412
1413     /* in case the video chain is waiting for a text buffer, wake it up */
1414     GST_ASS_RENDER_BROADCAST (render);
1415   }
1416
1417   GST_ASS_RENDER_UNLOCK (render);
1418
1419 beach:
1420   GST_DEBUG_OBJECT (render, "leaving chain for buffer %p", buffer);
1421
1422   gst_buffer_unref (buffer);
1423   return ret;
1424 }
1425
1426 static void
1427 gst_ass_render_handle_tags (GstAssRender * render, GstTagList * taglist)
1428 {
1429   static const gchar *mimetypes[] = {
1430     "application/x-font-ttf",
1431     "application/x-font-otf",
1432     "application/x-truetype-font"
1433   };
1434   static const gchar *extensions[] = {
1435     ".otf",
1436     ".ttf"
1437   };
1438   guint tag_size;
1439
1440   if (!taglist)
1441     return;
1442
1443   tag_size = gst_tag_list_get_tag_size (taglist, GST_TAG_ATTACHMENT);
1444   if (tag_size > 0 && render->embeddedfonts) {
1445     GstSample *sample;
1446     GstBuffer *buf;
1447     const GstStructure *structure;
1448     gboolean valid_mimetype, valid_extension;
1449     guint j;
1450     const gchar *filename;
1451     guint index;
1452     GstMapInfo map;
1453
1454     GST_DEBUG_OBJECT (render, "TAG event has attachments");
1455
1456     for (index = 0; index < tag_size; index++) {
1457       if (!gst_tag_list_get_sample_index (taglist, GST_TAG_ATTACHMENT, index,
1458               &sample))
1459         continue;
1460       buf = gst_sample_get_buffer (sample);
1461       structure = gst_sample_get_info (sample);
1462       if (!buf || !structure)
1463         continue;
1464
1465       valid_mimetype = FALSE;
1466       valid_extension = FALSE;
1467
1468       for (j = 0; j < G_N_ELEMENTS (mimetypes); j++) {
1469         if (gst_structure_has_name (structure, mimetypes[j])) {
1470           valid_mimetype = TRUE;
1471           break;
1472         }
1473       }
1474       filename = gst_structure_get_string (structure, "filename");
1475       if (!filename)
1476         continue;
1477
1478       if (!valid_mimetype) {
1479         guint len = strlen (filename);
1480         const gchar *extension = filename + len - 4;
1481         for (j = 0; j < G_N_ELEMENTS (extensions); j++) {
1482           if (g_ascii_strcasecmp (extension, extensions[j]) == 0) {
1483             valid_extension = TRUE;
1484             break;
1485           }
1486         }
1487       }
1488
1489       if (valid_mimetype || valid_extension) {
1490         g_mutex_lock (&render->ass_mutex);
1491         gst_buffer_map (buf, &map, GST_MAP_READ);
1492         ass_add_font (render->ass_library, (gchar *) filename,
1493             (gchar *) map.data, map.size);
1494         gst_buffer_unmap (buf, &map);
1495         GST_DEBUG_OBJECT (render, "registered new font %s", filename);
1496         g_mutex_unlock (&render->ass_mutex);
1497       }
1498     }
1499   }
1500 }
1501
1502 static gboolean
1503 gst_ass_render_event_video (GstPad * pad, GstObject * parent, GstEvent * event)
1504 {
1505   gboolean ret = FALSE;
1506   GstAssRender *render = GST_ASS_RENDER (parent);
1507
1508   GST_DEBUG_OBJECT (pad, "received video event %" GST_PTR_FORMAT, event);
1509
1510   switch (GST_EVENT_TYPE (event)) {
1511     case GST_EVENT_CAPS:
1512     {
1513       GstCaps *caps;
1514
1515       gst_event_parse_caps (event, &caps);
1516       ret = gst_ass_render_setcaps_video (pad, render, caps);
1517       gst_event_unref (event);
1518       break;
1519     }
1520     case GST_EVENT_SEGMENT:
1521     {
1522       GstSegment segment;
1523
1524       GST_DEBUG_OBJECT (render, "received new segment");
1525
1526       gst_event_copy_segment (event, &segment);
1527
1528       if (segment.format == GST_FORMAT_TIME) {
1529         GST_DEBUG_OBJECT (render, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1530             &render->video_segment);
1531
1532         render->video_segment = segment;
1533
1534         GST_DEBUG_OBJECT (render, "VIDEO SEGMENT after: %" GST_SEGMENT_FORMAT,
1535             &render->video_segment);
1536         ret = gst_pad_event_default (pad, parent, event);
1537       } else {
1538         GST_ELEMENT_WARNING (render, STREAM, MUX, (NULL),
1539             ("received non-TIME newsegment event on video input"));
1540         ret = FALSE;
1541         gst_event_unref (event);
1542       }
1543       break;
1544     }
1545     case GST_EVENT_TAG:
1546     {
1547       GstTagList *taglist = NULL;
1548
1549       /* tag events may contain attachments which might be fonts */
1550       GST_DEBUG_OBJECT (render, "got TAG event");
1551
1552       gst_event_parse_tag (event, &taglist);
1553       gst_ass_render_handle_tags (render, taglist);
1554       ret = gst_pad_event_default (pad, parent, event);
1555       break;
1556     }
1557     case GST_EVENT_EOS:
1558       GST_ASS_RENDER_LOCK (render);
1559       GST_INFO_OBJECT (render, "video EOS");
1560       render->video_eos = TRUE;
1561       GST_ASS_RENDER_UNLOCK (render);
1562       ret = gst_pad_event_default (pad, parent, event);
1563       break;
1564     case GST_EVENT_FLUSH_START:
1565       GST_ASS_RENDER_LOCK (render);
1566       GST_INFO_OBJECT (render, "video flush start");
1567       render->video_flushing = TRUE;
1568       GST_ASS_RENDER_BROADCAST (render);
1569       GST_ASS_RENDER_UNLOCK (render);
1570       ret = gst_pad_event_default (pad, parent, event);
1571       break;
1572     case GST_EVENT_FLUSH_STOP:
1573       GST_ASS_RENDER_LOCK (render);
1574       GST_INFO_OBJECT (render, "video flush stop");
1575       render->video_flushing = FALSE;
1576       render->video_eos = FALSE;
1577       gst_segment_init (&render->video_segment, GST_FORMAT_TIME);
1578       GST_ASS_RENDER_UNLOCK (render);
1579       ret = gst_pad_event_default (pad, parent, event);
1580       break;
1581     default:
1582       ret = gst_pad_event_default (pad, parent, event);
1583       break;
1584   }
1585
1586   return ret;
1587 }
1588
1589 static gboolean
1590 gst_ass_render_query_video (GstPad * pad, GstObject * parent, GstQuery * query)
1591 {
1592   gboolean res = FALSE;
1593
1594   switch (GST_QUERY_TYPE (query)) {
1595     case GST_QUERY_CAPS:
1596     {
1597       GstCaps *filter, *caps;
1598
1599       gst_query_parse_caps (query, &filter);
1600       caps =
1601           gst_ass_render_get_videosink_caps (pad, (GstAssRender *) parent,
1602           filter);
1603       gst_query_set_caps_result (query, caps);
1604       gst_caps_unref (caps);
1605       res = TRUE;
1606       break;
1607     }
1608     default:
1609       res = gst_pad_query_default (pad, parent, query);
1610       break;
1611   }
1612
1613   return res;
1614 }
1615
1616 static gboolean
1617 gst_ass_render_event_text (GstPad * pad, GstObject * parent, GstEvent * event)
1618 {
1619   gint i;
1620   gboolean ret = FALSE;
1621   GstAssRender *render = GST_ASS_RENDER (parent);
1622
1623   GST_DEBUG_OBJECT (pad, "received text event %" GST_PTR_FORMAT, event);
1624
1625   switch (GST_EVENT_TYPE (event)) {
1626     case GST_EVENT_CAPS:
1627     {
1628       GstCaps *caps;
1629
1630       gst_event_parse_caps (event, &caps);
1631       ret = gst_ass_render_setcaps_text (pad, render, caps);
1632       gst_event_unref (event);
1633       break;
1634     }
1635     case GST_EVENT_SEGMENT:
1636     {
1637       GstSegment segment;
1638
1639       GST_ASS_RENDER_LOCK (render);
1640       render->subtitle_eos = FALSE;
1641       GST_ASS_RENDER_UNLOCK (render);
1642
1643       gst_event_copy_segment (event, &segment);
1644
1645       GST_ASS_RENDER_LOCK (render);
1646       if (segment.format == GST_FORMAT_TIME) {
1647         GST_DEBUG_OBJECT (render, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1648             &render->subtitle_segment);
1649
1650         render->subtitle_segment = segment;
1651
1652         GST_DEBUG_OBJECT (render,
1653             "TEXT SEGMENT after: %" GST_SEGMENT_FORMAT,
1654             &render->subtitle_segment);
1655       } else {
1656         GST_ELEMENT_WARNING (render, STREAM, MUX, (NULL),
1657             ("received non-TIME newsegment event on subtitle input"));
1658       }
1659
1660       gst_event_unref (event);
1661       ret = TRUE;
1662
1663       /* wake up the video chain, it might be waiting for a text buffer or
1664        * a text segment update */
1665       GST_ASS_RENDER_BROADCAST (render);
1666       GST_ASS_RENDER_UNLOCK (render);
1667       break;
1668     }
1669     case GST_EVENT_GAP:{
1670       GstClockTime start, duration;
1671
1672       gst_event_parse_gap (event, &start, &duration);
1673       if (GST_CLOCK_TIME_IS_VALID (duration))
1674         start += duration;
1675       /* we do not expect another buffer until after gap,
1676        * so that is our position now */
1677       GST_ASS_RENDER_LOCK (render);
1678       render->subtitle_segment.position = start;
1679
1680       /* wake up the video chain, it might be waiting for a text buffer or
1681        * a text segment update */
1682       GST_ASS_RENDER_BROADCAST (render);
1683       GST_ASS_RENDER_UNLOCK (render);
1684
1685       gst_event_unref (event);
1686       ret = TRUE;
1687       break;
1688     }
1689     case GST_EVENT_FLUSH_STOP:
1690       GST_ASS_RENDER_LOCK (render);
1691       GST_INFO_OBJECT (render, "text flush stop");
1692       render->subtitle_flushing = FALSE;
1693       render->subtitle_eos = FALSE;
1694       gst_ass_render_pop_text (render);
1695       gst_segment_init (&render->subtitle_segment, GST_FORMAT_TIME);
1696       GST_ASS_RENDER_UNLOCK (render);
1697       gst_event_unref (event);
1698       ret = TRUE;
1699       break;
1700     case GST_EVENT_FLUSH_START:
1701       GST_DEBUG_OBJECT (render, "text flush start");
1702       g_mutex_lock (&render->ass_mutex);
1703       if (render->ass_track) {
1704         /* delete any events on the ass_track */
1705         for (i = 0; i < render->ass_track->n_events; i++) {
1706           GST_DEBUG_OBJECT (render, "deleted event with eid %i", i);
1707           ass_free_event (render->ass_track, i);
1708         }
1709         render->ass_track->n_events = 0;
1710         GST_DEBUG_OBJECT (render, "done flushing");
1711       }
1712       g_mutex_unlock (&render->ass_mutex);
1713       GST_ASS_RENDER_LOCK (render);
1714       render->subtitle_flushing = TRUE;
1715       GST_ASS_RENDER_BROADCAST (render);
1716       GST_ASS_RENDER_UNLOCK (render);
1717       gst_event_unref (event);
1718       ret = TRUE;
1719       break;
1720     case GST_EVENT_EOS:
1721       GST_ASS_RENDER_LOCK (render);
1722       render->subtitle_eos = TRUE;
1723       GST_INFO_OBJECT (render, "text EOS");
1724       /* wake up the video chain, it might be waiting for a text buffer or
1725        * a text segment update */
1726       GST_ASS_RENDER_BROADCAST (render);
1727       GST_ASS_RENDER_UNLOCK (render);
1728       gst_event_unref (event);
1729       ret = TRUE;
1730       break;
1731     case GST_EVENT_TAG:
1732     {
1733       GstTagList *taglist = NULL;
1734
1735       /* tag events may contain attachments which might be fonts */
1736       GST_DEBUG_OBJECT (render, "got TAG event");
1737
1738       gst_event_parse_tag (event, &taglist);
1739       gst_ass_render_handle_tags (render, taglist);
1740       ret = gst_pad_event_default (pad, parent, event);
1741       break;
1742     }
1743     default:
1744       ret = gst_pad_event_default (pad, parent, event);
1745       break;
1746   }
1747
1748   return ret;
1749 }
1750
1751 static gboolean
1752 plugin_init (GstPlugin * plugin)
1753 {
1754   GST_DEBUG_CATEGORY_INIT (gst_ass_render_debug, "assrender",
1755       0, "ASS/SSA subtitle renderer");
1756   GST_DEBUG_CATEGORY_INIT (gst_ass_render_lib_debug, "assrender_library",
1757       0, "ASS/SSA subtitle renderer library");
1758
1759   return gst_element_register (plugin, "assrender",
1760       GST_RANK_PRIMARY, GST_TYPE_ASS_RENDER);
1761 }
1762
1763 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1764     GST_VERSION_MINOR,
1765     assrender,
1766     "ASS/SSA subtitle renderer",
1767     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)