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