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