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