From 18881b687a36d643bc6f35c0d693d27e3376b87c Mon Sep 17 00:00:00 2001 From: "Ronald S. Bultje" Date: Sat, 8 Jan 2005 18:22:41 +0000 Subject: [PATCH] OGM text support, Matroska UTF-8 text support, deadlock fixes all over the place, subtitle awareness in decodebin/pla... Original commit message from CVS: * configure.ac: * ext/ogg/gstoggdemux.c: (gst_ogg_pad_new): * ext/ogg/gstogmparse.c: (gst_ogm_text_parse_get_type), (gst_ogm_text_parse_base_init), (gst_ogm_text_parse_init), (gst_ogm_parse_get_sink_querytypes), (gst_ogm_parse_sink_convert), (gst_ogm_parse_sink_query), (gst_ogm_parse_chain), (gst_ogm_parse_plugin_init): * ext/pango/gsttextoverlay.c: (gst_textoverlay_linkedpads), (gst_textoverlay_link), (gst_textoverlay_getcaps), (gst_textoverlay_event), (gst_textoverlay_video_chain), (gst_textoverlay_loop), (gst_textoverlay_init), (plugin_init): * ext/pango/gsttextoverlay.h: * gst/matroska/matroska-demux.c: (gst_matroska_demux_add_stream), (gst_matroska_demux_handle_seek_event), (gst_matroska_demux_sync_streams), (gst_matroska_demux_parse_blockgroup), (gst_matroska_demux_subtitle_caps), (gst_matroska_demux_plugin_init): * gst/matroska/matroska-ids.h: * gst/playback/gstdecodebin.c: (close_pad_link): * gst/playback/gstplaybasebin.c: (gst_play_base_bin_init), (gen_preroll_element), (remove_groups), (add_stream), (new_decoded_pad), (setup_subtitles), (gen_source_element), (setup_source): * gst/playback/gstplaybasebin.h: * gst/playback/gstplaybin.c: (gen_text_element), (setup_sinks): * gst/subparse/Makefile.am: * gst/subparse/gstsubparse.c: (gst_subparse_get_type), (gst_subparse_base_init), (gst_subparse_class_init), (gst_subparse_init), (gst_subparse_formats), (gst_subparse_eventmask), (gst_subparse_event), (gst_subparse_handle_event), (convert_encoding), (get_next_line), (parse_mdvdsub), (parse_mdvdsub_init), (parse_subrip), (parse_subrip_deinit), (parse_subrip_init), (parse_mpsub), (parse_mpsub_deinit), (parse_mpsub_init), (gst_subparse_buffer_format_autodetect), (gst_subparse_format_autodetect), (gst_subparse_loop), (gst_subparse_change_state), (gst_subparse_type_find), (plugin_init): * gst/subparse/gstsubparse.h: * gst/typefind/gsttypefindfunctions.c: (ogmtext_type_find), (plugin_init): Add subtitle support, .sub parser (supports SRT and MPsub), OGM text support, Matroska UTF-8 text support, deadlock fixes all over the place, subtitle awareness in decodebin/playbin and some fixes to textoverlay to handle subtitles in a stream correctly. Fixes #100931. --- ChangeLog | 50 ++ configure.ac | 2 + ext/ogg/gstoggdemux.c | 49 +- ext/ogg/gstogmparse.c | 170 +++++- ext/pango/gsttextoverlay.c | 238 +++++++-- ext/pango/gsttextoverlay.h | 4 +- gst/playback/gstdecodebin.c | 3 +- gst/playback/gstplaybasebin.c | 104 +++- gst/playback/gstplaybasebin.h | 2 + gst/playback/gstplaybin.c | 68 ++- gst/subparse/Makefile.am | 11 + gst/subparse/gstsubparse.c | 778 ++++++++++++++++++++++++++++ gst/subparse/gstsubparse.h | 87 ++++ gst/typefind/gsttypefindfunctions.c | 17 + 14 files changed, 1503 insertions(+), 80 deletions(-) create mode 100644 gst/subparse/Makefile.am create mode 100644 gst/subparse/gstsubparse.c create mode 100644 gst/subparse/gstsubparse.h diff --git a/ChangeLog b/ChangeLog index 90155d2e9e..4f3187e549 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,53 @@ +2005-01-08 Ronald S. Bultje + + * configure.ac: + * ext/ogg/gstoggdemux.c: (gst_ogg_pad_new): + * ext/ogg/gstogmparse.c: (gst_ogm_text_parse_get_type), + (gst_ogm_text_parse_base_init), (gst_ogm_text_parse_init), + (gst_ogm_parse_get_sink_querytypes), (gst_ogm_parse_sink_convert), + (gst_ogm_parse_sink_query), (gst_ogm_parse_chain), + (gst_ogm_parse_plugin_init): + * ext/pango/gsttextoverlay.c: (gst_textoverlay_linkedpads), + (gst_textoverlay_link), (gst_textoverlay_getcaps), + (gst_textoverlay_event), (gst_textoverlay_video_chain), + (gst_textoverlay_loop), (gst_textoverlay_init), (plugin_init): + * ext/pango/gsttextoverlay.h: + * gst/matroska/matroska-demux.c: (gst_matroska_demux_add_stream), + (gst_matroska_demux_handle_seek_event), + (gst_matroska_demux_sync_streams), + (gst_matroska_demux_parse_blockgroup), + (gst_matroska_demux_subtitle_caps), + (gst_matroska_demux_plugin_init): + * gst/matroska/matroska-ids.h: + * gst/playback/gstdecodebin.c: (close_pad_link): + * gst/playback/gstplaybasebin.c: (gst_play_base_bin_init), + (gen_preroll_element), (remove_groups), (add_stream), + (new_decoded_pad), (setup_subtitles), (gen_source_element), + (setup_source): + * gst/playback/gstplaybasebin.h: + * gst/playback/gstplaybin.c: (gen_text_element), (setup_sinks): + * gst/subparse/Makefile.am: + * gst/subparse/gstsubparse.c: (gst_subparse_get_type), + (gst_subparse_base_init), (gst_subparse_class_init), + (gst_subparse_init), (gst_subparse_formats), + (gst_subparse_eventmask), (gst_subparse_event), + (gst_subparse_handle_event), (convert_encoding), (get_next_line), + (parse_mdvdsub), (parse_mdvdsub_init), (parse_subrip), + (parse_subrip_deinit), (parse_subrip_init), (parse_mpsub), + (parse_mpsub_deinit), (parse_mpsub_init), + (gst_subparse_buffer_format_autodetect), + (gst_subparse_format_autodetect), (gst_subparse_loop), + (gst_subparse_change_state), (gst_subparse_type_find), + (plugin_init): + * gst/subparse/gstsubparse.h: + * gst/typefind/gsttypefindfunctions.c: (ogmtext_type_find), + (plugin_init): + Add subtitle support, .sub parser (supports SRT and MPsub), + OGM text support, Matroska UTF-8 text support, deadlock fixes + all over the place, subtitle awareness in decodebin/playbin + and some fixes to textoverlay to handle subtitles in a stream + correctly. Fixes #100931. + 2005-01-08 Ronald S. Bultje * ext/vorbis/vorbisdec.c: (vorbis_dec_src_query): diff --git a/configure.ac b/configure.ac index f730ddb976..b50d45f0f6 100644 --- a/configure.ac +++ b/configure.ac @@ -408,6 +408,7 @@ GST_PLUGINS_ALL="\ spectrum \ speed \ stereo \ + subparse \ switch \ synaesthesia \ tags \ @@ -1972,6 +1973,7 @@ gst/smpte/Makefile gst/spectrum/Makefile gst/speed/Makefile gst/stereo/Makefile +gst/subparse/Makefile gst/switch/Makefile gst/synaesthesia/Makefile gst/tags/Makefile diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index dd8a70120e..fc22cbab46 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -614,12 +614,10 @@ gst_ogg_demux_src_event (GstPad * pad, GstEvent * event) GST_OGG_SET_STATE (ogg, GST_OGG_STATE_SEEK); FOR_PAD_IN_CURRENT_CHAIN (ogg, pad, - pad->flags |= GST_OGG_PAD_NEEDS_DISCONT; - ); + pad->flags |= GST_OGG_PAD_NEEDS_DISCONT;); if (GST_EVENT_SEEK_FLAGS (event) & GST_SEEK_FLAG_FLUSH) { FOR_PAD_IN_CURRENT_CHAIN (ogg, pad, - pad->flags |= GST_OGG_PAD_NEEDS_FLUSH; - ); + pad->flags |= GST_OGG_PAD_NEEDS_FLUSH;); } GST_DEBUG_OBJECT (ogg, "initiating seeking to format %d, offset %" G_GUINT64_FORMAT, format, @@ -694,8 +692,7 @@ gst_ogg_demux_handle_event (GstPad * pad, GstEvent * event) gst_event_unref (event); GST_FLAG_UNSET (ogg, GST_OGG_FLAG_WAIT_FOR_DISCONT); FOR_PAD_IN_CURRENT_CHAIN (ogg, pad, - pad->flags |= GST_OGG_PAD_NEEDS_DISCONT; - ); + pad->flags |= GST_OGG_PAD_NEEDS_DISCONT;); break; default: gst_pad_event_default (pad, event); @@ -981,8 +978,7 @@ _find_chain_get_unknown_part (GstOggDemux * ogg, gint64 * start, gint64 * end) *end = G_MAXINT64; g_assert (ogg->current_chain >= 0); - FOR_PAD_IN_CURRENT_CHAIN (ogg, pad, *start = MAX (*start, pad->end_offset); - ); + FOR_PAD_IN_CURRENT_CHAIN (ogg, pad, *start = MAX (*start, pad->end_offset);); if (ogg->setup_state == SETUP_FIND_LAST_CHAIN) { *end = gst_file_pad_get_length (ogg->sinkpad); @@ -1111,8 +1107,7 @@ _find_streams_check (GstOggDemux * ogg) } else { endpos = G_MAXINT64; FOR_PAD_IN_CHAIN (ogg, pad, ogg->chains->len - 1, - endpos = MIN (endpos, pad->start_offset); - ); + endpos = MIN (endpos, pad->start_offset);); } if (!ogg->seek_skipped || gst_ogg_demux_position (ogg) >= endpos) { /* have we found the endposition for all streams yet? */ @@ -1308,6 +1303,7 @@ gst_ogg_pad_new (GstOggDemux * ogg, int serial) ret->start_offset = ret->end_offset = -1; ret->start = -1; ret->start_found = ret->end_found = FALSE; + ret->offset = GST_BUFFER_OFFSET_NONE; return ret; } @@ -1461,6 +1457,37 @@ gst_ogg_demux_push (GstOggDemux * ogg, ogg_page * page) cur->pad = NULL; } } + +static void +gst_ogg_sync (GstOggDemux * ogg, GstOggPad * cur) +{ + gint64 bias, time; + GstFormat fmt = GST_FORMAT_TIME; + + time = get_relative (ogg, cur, cur->offset, GST_FORMAT_TIME); + FOR_PAD_IN_CURRENT_CHAIN (ogg, pad, + if (pad->pad && GST_PAD_PEER (pad->pad) && + pad->offset != GST_BUFFER_OFFSET_NONE) { + if (gst_pad_query (GST_PAD_PEER (pad->pad), + GST_QUERY_POSITION, &fmt, &bias) && bias + GST_SECOND < time) { + GstEvent * event; + gint64 val = 0; + GstFormat fmt = GST_FORMAT_DEFAULT; + event = gst_event_new_filler_stamped (bias, + time - bias - GST_SECOND / 2); + GST_DEBUG ("Syncing stream %d at time %" GST_TIME_FORMAT + " and duration %" GST_TIME_FORMAT, + pad->serial, GST_TIME_ARGS (bias), + GST_TIME_ARGS (time - bias - GST_SECOND / 2)); + gst_pad_push (pad->pad, GST_DATA (event)); + gst_pad_convert (GST_PAD_PEER (pad->pad), + GST_FORMAT_TIME, bias, &fmt, &val); + /* hmm... */ + pad->offset = pad->start + val;} + } + ); +} + static void gst_ogg_pad_push (GstOggDemux * ogg, GstOggPad * pad) { @@ -1554,6 +1581,7 @@ gst_ogg_pad_push (GstOggDemux * ogg, GstOggPad * pad) } else { event = gst_event_new_discontinuous (FALSE, GST_FORMAT_DEFAULT, discont, GST_FORMAT_UNDEFINED); + pad->offset = discont; } } else { event = gst_event_new_discontinuous (FALSE, @@ -1577,6 +1605,7 @@ gst_ogg_pad_push (GstOggDemux * ogg, GstOggPad * pad) if (packet.granulepos != -1 && pos != -1) GST_BUFFER_OFFSET_END (buf) = pos; pad->offset = packet.granulepos; + gst_ogg_sync (ogg, pad); if (GST_PAD_IS_USABLE (pad->pad)) gst_pad_push (pad->pad, GST_DATA (buf)); break; diff --git a/ext/ogg/gstogmparse.c b/ext/ogg/gstogmparse.c index 44dddc8ea3..d8f257e6b3 100644 --- a/ext/ogg/gstogmparse.c +++ b/ext/ogg/gstogmparse.c @@ -39,6 +39,10 @@ GST_DEBUG_CATEGORY_STATIC (gst_ogm_parse_debug); #define GST_IS_OGM_AUDIO_PARSE(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OGM_AUDIO_PARSE)) +#define GST_TYPE_OGM_TEXT_PARSE (gst_ogm_text_parse_get_type()) +#define GST_IS_OGM_TEXT_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OGM_TEXT_PARSE)) + #define GST_TYPE_OGM_PARSE (gst_ogm_parse_get_type()) #define GST_OGM_PARSE(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_OGM_PARSE, GstOgmParse)) @@ -87,6 +91,7 @@ typedef struct _stream_header { stream_header_video video; stream_header_audio audio; + /* text has no additional data */ } s; } stream_header; @@ -116,22 +121,31 @@ GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, static GstStaticPadTemplate ogm_audio_parse_sink_template_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("application/x-ogm-audio")); -static GstPadTemplate *video_src_templ, *audio_src_templ; +static GstStaticPadTemplate ogm_text_parse_sink_template_factory = +GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-ogm-text")); +static GstPadTemplate *video_src_templ, *audio_src_templ, *text_src_templ; static GType gst_ogm_audio_parse_get_type (void); static GType gst_ogm_video_parse_get_type (void); +static GType gst_ogm_text_parse_get_type (void); static GType gst_ogm_parse_get_type (void); static void gst_ogm_audio_parse_base_init (GstOgmParseClass * klass); static void gst_ogm_video_parse_base_init (GstOgmParseClass * klass); +static void gst_ogm_text_parse_base_init (GstOgmParseClass * klass); static void gst_ogm_parse_class_init (GstOgmParseClass * klass); static void gst_ogm_parse_init (GstOgmParse * ogm); static void gst_ogm_video_parse_init (GstOgmParse * ogm); static void gst_ogm_audio_parse_init (GstOgmParse * ogm); +static void gst_ogm_text_parse_init (GstOgmParse * ogm); static const GstFormat *gst_ogm_parse_get_sink_formats (GstPad * pad); +static const GstQueryType *gst_ogm_parse_get_sink_querytypes (GstPad * pad); static gboolean gst_ogm_parse_sink_convert (GstPad * pad, GstFormat src_format, gint64 src_value, GstFormat * dest_format, gint64 * dest_value); +static gboolean gst_ogm_parse_sink_query (GstPad * pad, GstQueryType type, + GstFormat * fmt, gint64 * val); static void gst_ogm_parse_chain (GstPad * pad, GstData * data); @@ -217,6 +231,32 @@ gst_ogm_video_parse_get_type (void) return ogm_video_parse_type; } +GType +gst_ogm_text_parse_get_type (void) +{ + static GType ogm_text_parse_type = 0; + + if (!ogm_text_parse_type) { + static const GTypeInfo ogm_text_parse_info = { + sizeof (GstOgmParseClass), + (GBaseInitFunc) gst_ogm_text_parse_base_init, + NULL, + NULL, + NULL, + NULL, + sizeof (GstOgmParse), + 0, + (GInstanceInitFunc) gst_ogm_text_parse_init, + }; + + ogm_text_parse_type = + g_type_register_static (GST_TYPE_OGM_PARSE, + "GstOgmTextParse", &ogm_text_parse_info, 0); + } + + return ogm_text_parse_type; +} + static void gst_ogm_audio_parse_base_init (GstOgmParseClass * klass) { @@ -257,6 +297,26 @@ gst_ogm_video_parse_base_init (GstOgmParseClass * klass) gst_element_class_add_pad_template (element_class, video_src_templ); } +static void +gst_ogm_text_parse_base_init (GstOgmParseClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + static GstElementDetails gst_ogm_text_parse_details = + GST_ELEMENT_DETAILS ("OGM text stream parser", + "Codec/Decoder/Subtitle", + "parse an OGM text header and stream", + "Ronald Bultje "); + GstCaps *caps = gst_caps_new_simple ("text/plain", NULL); + + gst_element_class_set_details (element_class, &gst_ogm_text_parse_details); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&ogm_text_parse_sink_template_factory)); + text_src_templ = gst_pad_template_new ("src", + GST_PAD_SRC, GST_PAD_SOMETIMES, caps); + gst_element_class_add_pad_template (element_class, text_src_templ); +} + static void gst_ogm_parse_class_init (GstOgmParseClass * klass) { @@ -317,6 +377,30 @@ gst_ogm_video_parse_init (GstOgmParse * ogm) ogm->srcpadtempl = video_src_templ; } +static void +gst_ogm_text_parse_init (GstOgmParse * ogm) +{ + GstPadTemplate *templ; + + /* create the pads */ + templ = gst_static_pad_template_get (&ogm_text_parse_sink_template_factory); + ogm->sinkpad = gst_pad_new_from_template (templ, "sink"); + gst_pad_set_convert_function (ogm->sinkpad, gst_ogm_parse_sink_convert); + gst_pad_set_formats_function (ogm->sinkpad, gst_ogm_parse_get_sink_formats); + gst_pad_set_query_type_function (ogm->sinkpad, + gst_ogm_parse_get_sink_querytypes); + gst_pad_set_query_function (ogm->sinkpad, gst_ogm_parse_sink_query); + gst_pad_set_chain_function (ogm->sinkpad, gst_ogm_parse_chain); + gst_element_add_pad (GST_ELEMENT (ogm), ogm->sinkpad); + +#if 0 + ogm->srcpad = gst_pad_new_from_template (text_src_templ, "src"); + gst_pad_use_explicit_caps (ogm->srcpad); + gst_element_add_pad (GST_ELEMENT (ogm), ogm->srcpad); +#endif + ogm->srcpadtempl = text_src_templ; +} + static const GstFormat * gst_ogm_parse_get_sink_formats (GstPad * pad) { @@ -329,6 +413,17 @@ gst_ogm_parse_get_sink_formats (GstPad * pad) return formats; } +static const GstQueryType * +gst_ogm_parse_get_sink_querytypes (GstPad * pad) +{ + static const GstQueryType types[] = { + GST_QUERY_POSITION, + 0 + }; + + return types; +} + static gboolean gst_ogm_parse_sink_convert (GstPad * pad, GstFormat src_format, gint64 src_value, @@ -347,6 +442,7 @@ gst_ogm_parse_sink_convert (GstPad * pad, res = TRUE; break; case 'v': + case 't': *dest_value = (GST_SECOND / 10000000) * ogm->hdr.time_unit * src_value; res = TRUE; @@ -359,6 +455,28 @@ gst_ogm_parse_sink_convert (GstPad * pad, break; } break; + case GST_FORMAT_TIME: + switch (*dest_format) { + case GST_FORMAT_DEFAULT: + switch (ogm->hdr.streamtype[0]) { + case 'a': + *dest_value = ogm->hdr.samples_per_unit * src_value / GST_SECOND; + res = TRUE; + break; + case 'v': + case 't': + *dest_value = src_value / + ((GST_SECOND / 10000000) * ogm->hdr.time_unit); + res = TRUE; + break; + default: + break; + } + break; + default: + break; + } + break; default: break; } @@ -366,6 +484,21 @@ gst_ogm_parse_sink_convert (GstPad * pad, return res; } +static gboolean +gst_ogm_parse_sink_query (GstPad * pad, + GstQueryType type, GstFormat * fmt, gint64 * val) +{ + GstOgmParse *ogm = GST_OGM_PARSE (gst_pad_get_parent (pad)); + + if (type != GST_QUERY_POSITION) + return FALSE; + if (*fmt != GST_FORMAT_DEFAULT && *fmt != GST_FORMAT_TIME) + return FALSE; + + return gst_pad_convert (pad, + GST_FORMAT_DEFAULT, ogm->next_granulepos, fmt, val); +} + static void gst_ogm_parse_chain (GstPad * pad, GstData * dat) { @@ -394,6 +527,8 @@ gst_ogm_parse_chain (GstPad * pad, GstData * dat) ogm->hdr.s.audio.channels = GST_READ_UINT32_LE (&data[45]); ogm->hdr.s.audio.blockalign = GST_READ_UINT32_LE (&data[47]); ogm->hdr.s.audio.avgbytespersec = GST_READ_UINT32_LE (&data[49]); + } else if (!memcmp (&data[1], "text\000\000\000\000", 8)) { + /* nothing here */ } else { GST_ELEMENT_ERROR (ogm, STREAM, WRONG_TYPE, ("Unknown stream type"), (NULL)); @@ -448,6 +583,13 @@ gst_ogm_parse_chain (GstPad * pad, GstData * dat) "framerate", G_TYPE_DOUBLE, 10000000. / ogm->hdr.time_unit, NULL); break; } + case 't': + GST_LOG_OBJECT (ogm, "Type: %s, s/u: %" G_GINT64_FORMAT + ", timeunit=%" G_GINT64_FORMAT, + ogm->hdr.streamtype, ogm->hdr.samples_per_unit, + ogm->hdr.time_unit); + caps = gst_caps_new_simple ("text/plain", NULL); + break; default: g_assert_not_reached (); } @@ -491,15 +633,19 @@ gst_ogm_parse_chain (GstPad * pad, GstData * dat) ogm->next_granulepos = GST_BUFFER_OFFSET_END (buf); } switch (ogm->hdr.streamtype[0]) { - case 'v': + case 't': + case 'v':{ + gint samples = (ogm->hdr.streamtype[0] == 'v') ? 1 : xsize; + if (keyframe) GST_BUFFER_FLAG_SET (sbuf, GST_BUFFER_KEY_UNIT); GST_BUFFER_TIMESTAMP (sbuf) = (GST_SECOND / 10000000) * ogm->next_granulepos * ogm->hdr.time_unit; GST_BUFFER_DURATION (sbuf) = (GST_SECOND / 10000000) * - ogm->hdr.time_unit; - ogm->next_granulepos++; + ogm->hdr.time_unit * samples; + ogm->next_granulepos += samples; break; + } case 'a': GST_BUFFER_TIMESTAMP (sbuf) = GST_SECOND * ogm->next_granulepos / ogm->hdr.samples_per_unit; @@ -508,9 +654,13 @@ gst_ogm_parse_chain (GstPad * pad, GstData * dat) ogm->next_granulepos += xsize; break; default: - g_assert_not_reached (); + gst_buffer_unref (sbuf); + sbuf = NULL; + GST_ELEMENT_ERROR (ogm, RESOURCE, SYNC, (NULL), (NULL)); + break; } - gst_pad_push (ogm->srcpad, GST_DATA (sbuf)); + if (sbuf) + gst_pad_push (ogm->srcpad, GST_DATA (sbuf)); } else { GST_ELEMENT_ERROR (ogm, STREAM, WRONG_TYPE, ("Wrong packet startcode 0x%02x", data[0]), (NULL)); @@ -548,7 +698,9 @@ gst_ogm_parse_plugin_init (GstPlugin * plugin) GST_DEBUG_CATEGORY_INIT (gst_ogm_parse_debug, "ogmparse", 0, "ogm parser"); return gst_element_register (plugin, "ogmaudioparse", GST_RANK_PRIMARY, - GST_TYPE_OGM_AUDIO_PARSE) - && gst_element_register (plugin, "ogmvideoparse", GST_RANK_PRIMARY, - GST_TYPE_OGM_VIDEO_PARSE); + GST_TYPE_OGM_AUDIO_PARSE) && + gst_element_register (plugin, "ogmvideoparse", GST_RANK_PRIMARY, + GST_TYPE_OGM_VIDEO_PARSE) && + gst_element_register (plugin, "ogmtextparse", GST_RANK_PRIMARY, + GST_TYPE_OGM_TEXT_PARSE); } diff --git a/ext/pango/gsttextoverlay.c b/ext/pango/gsttextoverlay.c index 52f257a7ae..bbd92b02e2 100644 --- a/ext/pango/gsttextoverlay.c +++ b/ext/pango/gsttextoverlay.c @@ -24,6 +24,9 @@ #include #include "gsttextoverlay.h" +GST_DEBUG_CATEGORY_STATIC (pango_debug); +#define GST_CAT_DEFAULT pango_debug + static GstElementDetails textoverlay_details = { "Text Overlay", "Filter/Editor/Video", @@ -224,23 +227,78 @@ render_text (GstTextOverlay * overlay) /* return GST_PAD_LINK_DONE; */ /* } */ - -static GstPadLinkReturn -gst_textoverlay_video_sinkconnect (GstPad * pad, const GstCaps * caps) +static GList * +gst_textoverlay_linkedpads (GstPad * pad) { + GstPad *otherpad; GstTextOverlay *overlay; - GstStructure *structure; overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad)); + if (pad == overlay->text_sinkpad) + return NULL; + otherpad = (pad == overlay->video_sinkpad) ? + overlay->srcpad : overlay->video_sinkpad; + + return g_list_append (NULL, otherpad); +} + +static GstPadLinkReturn +gst_textoverlay_link (GstPad * pad, const GstCaps * caps) +{ + GstPad *otherpad; + GstTextOverlay *overlay; + GstStructure *structure; + GstPadLinkReturn ret; + + overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad)); + otherpad = (pad == overlay->video_sinkpad) ? + overlay->srcpad : overlay->video_sinkpad; + + ret = gst_pad_try_set_caps (otherpad, caps); + if (GST_PAD_LINK_FAILED (ret)) + return ret; structure = gst_caps_get_structure (caps, 0); overlay->width = overlay->height = 0; gst_structure_get_int (structure, "width", &overlay->width); gst_structure_get_int (structure, "height", &overlay->height); - return gst_pad_try_set_caps (overlay->srcpad, caps); + return ret; } +static GstCaps * +gst_textoverlay_getcaps (GstPad * pad) +{ + GstPad *otherpad; + GstTextOverlay *overlay; + GstCaps *caps, *rcaps; + const GstCaps *tcaps; + + overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad)); + otherpad = (pad == overlay->video_sinkpad) ? + overlay->srcpad : overlay->video_sinkpad; + + caps = gst_pad_get_allowed_caps (otherpad); + tcaps = gst_pad_get_pad_template_caps (pad); + rcaps = gst_caps_intersect (caps, tcaps); + gst_caps_free (caps); + + return rcaps; +} + +static gboolean +gst_textoverlay_event (GstPad * pad, GstEvent * event) +{ + GstTextOverlay *overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad)); + + if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK && + GST_PAD_IS_LINKED (overlay->text_sinkpad)) { + gst_event_ref (event); + gst_pad_send_event (GST_PAD_PEER (overlay->text_sinkpad), event); + } + + return gst_pad_send_event (GST_PAD_PEER (overlay->video_sinkpad), event); +} static void gst_text_overlay_blit_yuv420 (GstTextOverlay * overlay, FT_Bitmap * bitmap, @@ -353,9 +411,9 @@ gst_textoverlay_video_chain (GstPad * pad, GstData * _data) y0 = overlay->y0; switch (overlay->valign) { case GST_TEXT_OVERLAY_VALIGN_BOTTOM: - y0 += overlay->bitmap.rows; + y0 = overlay->height - overlay->bitmap.rows - y0; break; - case GST_TEXT_OVERLAY_VALIGN_BASELINE: + case GST_TEXT_OVERLAY_VALIGN_BASELINE: /* ? */ y0 -= (overlay->bitmap.rows - overlay->baseline_y); break; case GST_TEXT_OVERLAY_VALIGN_TOP: @@ -366,10 +424,10 @@ gst_textoverlay_video_chain (GstPad * pad, GstData * _data) case GST_TEXT_OVERLAY_HALIGN_LEFT: break; case GST_TEXT_OVERLAY_HALIGN_RIGHT: - x0 -= overlay->bitmap.width; + x0 = overlay->width - overlay->bitmap.width - x0; break; case GST_TEXT_OVERLAY_HALIGN_CENTER: - x0 -= overlay->bitmap.width / 2; + x0 = (overlay->width - overlay->bitmap.width) / 2; break; } @@ -379,10 +437,19 @@ gst_textoverlay_video_chain (GstPad * pad, GstData * _data) gst_pad_push (overlay->srcpad, GST_DATA (buf)); } -#define PAST_END(buffer, time) \ - (GST_BUFFER_TIMESTAMP (buffer) != GST_CLOCK_TIME_NONE && \ - GST_BUFFER_DURATION (buffer) != GST_CLOCK_TIME_NONE && \ - GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) \ +#define GST_DATA_TIMESTAMP(data) \ + (GST_IS_EVENT (data) ? \ + GST_EVENT_TIMESTAMP (GST_EVENT (data)) : \ + GST_BUFFER_TIMESTAMP (GST_BUFFER (data))) +#define GST_DATA_DURATION(data) \ + (GST_IS_EVENT (data) ? \ + gst_event_filler_get_duration (GST_EVENT (data)) : \ + GST_BUFFER_DURATION (GST_BUFFER (data))) + +#define PAST_END(data, time) \ + (GST_DATA_TIMESTAMP (data) != GST_CLOCK_TIME_NONE && \ + GST_DATA_DURATION (data) != GST_CLOCK_TIME_NONE && \ + GST_DATA_TIMESTAMP (data) + GST_DATA_DURATION (data) \ < (time)) static void @@ -396,8 +463,36 @@ gst_textoverlay_loop (GstElement * element) g_return_if_fail (GST_IS_TEXTOVERLAY (element)); overlay = GST_TEXTOVERLAY (element); - video_frame = GST_BUFFER (gst_pad_pull (overlay->video_sinkpad)); + do { + GST_DEBUG ("Attempting to pull next video frame"); + video_frame = GST_BUFFER (gst_pad_pull (overlay->video_sinkpad)); + if (GST_IS_EVENT (video_frame)) { + GstEvent *event = GST_EVENT (video_frame); + GstEventType type = GST_EVENT_TYPE (event); + + gst_pad_event_default (overlay->video_sinkpad, event); + GST_DEBUG ("Received event of type %d", type); + if (type == GST_EVENT_INTERRUPT) + return; + else if (type == GST_EVENT_EOS) { + /* EOS text stream */ + GstData *data = NULL; + + do { + if (data) + gst_data_unref (data); + data = gst_pad_pull (overlay->text_sinkpad); + } while (!GST_IS_EVENT (data) || + GST_EVENT_TYPE (data) == GST_EVENT_EOS); + gst_data_unref (data); + + return; + } + video_frame = NULL; + } + } while (!video_frame); now = GST_BUFFER_TIMESTAMP (video_frame); + GST_DEBUG ("Got video frame, time=%" GST_TIME_FORMAT, GST_TIME_ARGS (now)); /* * This state machine has a bug that can't be resolved easily. @@ -410,49 +505,89 @@ gst_textoverlay_loop (GstElement * element) * buffer timestamps and durations correctly. (I think) */ - while (overlay->next_buffer == NULL) { - GST_DEBUG ("attempting to pull a buffer"); + while ((!overlay->current_data || + PAST_END (overlay->current_data, now)) && + overlay->next_data == NULL) { + GST_DEBUG ("attempting to pull text data"); /* read all text buffers until we get one "in the future" */ if (!GST_PAD_IS_USABLE (overlay->text_sinkpad)) { break; } - overlay->next_buffer = GST_BUFFER (gst_pad_pull (overlay->text_sinkpad)); - if (!overlay->next_buffer) - break; + do { + overlay->next_data = gst_pad_pull (overlay->text_sinkpad); + if (GST_IS_EVENT (overlay->next_data) && + GST_EVENT_TYPE (overlay->next_data) != GST_EVENT_FILLER) { + GstEvent *event = GST_EVENT (overlay->next_data); + GstEventType type = GST_EVENT_TYPE (event); - if (PAST_END (overlay->next_buffer, now)) { - gst_buffer_unref (overlay->next_buffer); - overlay->next_buffer = NULL; + gst_event_unref (event); + if (type == GST_EVENT_EOS) + break; + else if (type == GST_EVENT_INTERRUPT) + return; + overlay->next_data = NULL; + } + } while (!overlay->next_data); + + if (PAST_END (overlay->next_data, now)) { + GST_DEBUG ("Received %s is past end (%" GST_TIME_FORMAT " + %" + GST_TIME_FORMAT " < %" GST_TIME_FORMAT ")", + GST_IS_EVENT (overlay->next_data) ? "event" : "buffer", + GST_TIME_ARGS (GST_DATA_TIMESTAMP (overlay->next_data)), + GST_TIME_ARGS (GST_DATA_DURATION (overlay->next_data)), + GST_TIME_ARGS (now)); + gst_data_unref (overlay->next_data); + overlay->next_data = NULL; + } else { + GST_DEBUG ("Received new text %s of time %" GST_TIME_FORMAT + "and duration %" GST_TIME_FORMAT, + GST_IS_EVENT (overlay->next_data) ? "event" : "buffer", + GST_TIME_ARGS (GST_DATA_TIMESTAMP (overlay->next_data)), + GST_TIME_ARGS (GST_DATA_DURATION (overlay->next_data))); } } - if (overlay->next_buffer && - (GST_BUFFER_TIMESTAMP (overlay->next_buffer) <= now || - GST_BUFFER_TIMESTAMP (overlay->next_buffer) == GST_CLOCK_TIME_NONE)) { - GST_DEBUG ("using new buffer"); + if (overlay->next_data && + (GST_DATA_TIMESTAMP (overlay->next_data) <= now || + GST_DATA_TIMESTAMP (overlay->next_data) == GST_CLOCK_TIME_NONE)) { + GST_DEBUG ("using new %s", + GST_IS_EVENT (overlay->next_data) ? "event" : "buffer"); - if (overlay->current_buffer) { - gst_buffer_unref (overlay->current_buffer); + if (overlay->current_data) { + gst_data_unref (overlay->current_data); } - overlay->current_buffer = overlay->next_buffer; - overlay->next_buffer = NULL; + overlay->current_data = overlay->next_data; + overlay->next_data = NULL; - GST_DEBUG ("rendering '%*s'", - GST_BUFFER_SIZE (overlay->current_buffer), - GST_BUFFER_DATA (overlay->current_buffer)); - pango_layout_set_markup (overlay->layout, - GST_BUFFER_DATA (overlay->current_buffer), - GST_BUFFER_SIZE (overlay->current_buffer)); + if (GST_IS_BUFFER (overlay->current_data)) { + guint size = GST_BUFFER_SIZE (overlay->current_data); + guint8 *data = GST_BUFFER_DATA (overlay->current_data); + + while (size > 0 && + (data[size - 1] == '\r' || + data[size - 1] == '\n' || data[size - 1] == '\0')) + size--; + + GST_DEBUG ("rendering '%*s'", size, + GST_BUFFER_DATA (overlay->current_data)); + /* somehow pango barfs over "\0" buffers... */ + pango_layout_set_markup (overlay->layout, + GST_BUFFER_DATA (overlay->current_data), size); + } else { + GST_DEBUG ("Filler - no data"); + pango_layout_set_markup (overlay->layout, "", 0); + } render_text (overlay); overlay->need_render = FALSE; } - if (overlay->current_buffer && PAST_END (overlay->current_buffer, now)) { - GST_DEBUG ("dropping old buffer"); + if (overlay->current_data && PAST_END (overlay->current_data, now)) { + GST_DEBUG ("dropping old %s", + GST_IS_EVENT (overlay->current_data) ? "event" : "buffer"); - gst_buffer_unref (overlay->current_buffer); - overlay->current_buffer = NULL; + gst_buffer_unref (overlay->current_data); + overlay->current_data = NULL; overlay->need_render = TRUE; } @@ -515,22 +650,30 @@ gst_textoverlay_init (GstTextOverlay * overlay) overlay->video_sinkpad = gst_pad_new_from_template (gst_static_pad_template_get (&video_sink_template_factory), "video_sink"); -/* gst_pad_set_chain_function(overlay->video_sinkpad, gst_textoverlay_video_chain); */ - gst_pad_set_link_function (overlay->video_sinkpad, - gst_textoverlay_video_sinkconnect); + gst_pad_set_link_function (overlay->video_sinkpad, gst_textoverlay_link); + gst_pad_set_getcaps_function (overlay->video_sinkpad, + gst_textoverlay_getcaps); + gst_pad_set_internal_link_function (overlay->video_sinkpad, + gst_textoverlay_linkedpads); gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad); /* text sink */ overlay->text_sinkpad = gst_pad_new_from_template (gst_static_pad_template_get (&text_sink_template_factory), "text_sink"); -/* gst_pad_set_link_function(overlay->text_sinkpad, gst_textoverlay_text_sinkconnect); */ + gst_pad_set_internal_link_function (overlay->text_sinkpad, + gst_textoverlay_linkedpads); gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad); /* (video) source */ overlay->srcpad = gst_pad_new_from_template (gst_static_pad_template_get (&textoverlay_src_template_factory), "src"); + gst_pad_set_link_function (overlay->srcpad, gst_textoverlay_link); + gst_pad_set_getcaps_function (overlay->srcpad, gst_textoverlay_getcaps); + gst_pad_set_internal_link_function (overlay->srcpad, + gst_textoverlay_linkedpads); + gst_pad_set_event_function (overlay->srcpad, gst_textoverlay_event); gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad); overlay->layout = @@ -539,12 +682,14 @@ gst_textoverlay_init (GstTextOverlay * overlay) overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER; overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE; - overlay->x0 = overlay->y0 = 0; + overlay->x0 = overlay->y0 = 25; overlay->default_text = g_strdup (""); overlay->need_render = TRUE; gst_element_set_loop_function (GST_ELEMENT (overlay), gst_textoverlay_loop); + + GST_FLAG_SET (overlay, GST_ELEMENT_EVENT_AWARE); } @@ -647,6 +792,9 @@ plugin_init (GstPlugin * plugin) /*texttestsrc_plugin_init(module, plugin); */ /*subparse_plugin_init(module, plugin); */ + + GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements"); + return TRUE; } diff --git a/ext/pango/gsttextoverlay.h b/ext/pango/gsttextoverlay.h index df5b5b5525..669f88d446 100644 --- a/ext/pango/gsttextoverlay.h +++ b/ext/pango/gsttextoverlay.h @@ -54,8 +54,8 @@ struct _GstTextOverlay { GstTextOverlayHAlign halign; gint x0; gint y0; - GstBuffer *current_buffer; - GstBuffer *next_buffer; + GstData *current_data; + GstData *next_data; gchar *default_text; gboolean need_render; }; diff --git a/gst/playback/gstdecodebin.c b/gst/playback/gstdecodebin.c index 508cc16c26..faf4d2c45c 100644 --- a/gst/playback/gstdecodebin.c +++ b/gst/playback/gstdecodebin.c @@ -422,7 +422,8 @@ close_pad_link (GstElement * element, GstPad * pad, GstCaps * caps, /* first see if this is raw. If the type is raw, we can * create a ghostpad for this pad. */ if (g_str_has_prefix (mimetype, "video/x-raw") || - g_str_has_prefix (mimetype, "audio/x-raw")) { + g_str_has_prefix (mimetype, "audio/x-raw") || + g_str_has_prefix (mimetype, "text/plain")) { gchar *padname; GstPad *ghost; diff --git a/gst/playback/gstplaybasebin.c b/gst/playback/gstplaybasebin.c index faa166c581..76fe31a85f 100644 --- a/gst/playback/gstplaybasebin.c +++ b/gst/playback/gstplaybasebin.c @@ -195,6 +195,7 @@ gst_play_base_bin_init (GstPlayBaseBin * play_base_bin) play_base_bin->need_rebuild = TRUE; play_base_bin->source = NULL; play_base_bin->decoder = NULL; + play_base_bin->subtitles = NULL; play_base_bin->group_lock = g_mutex_new (); play_base_bin->group_cond = g_cond_new (); @@ -402,12 +403,10 @@ gen_preroll_element (GstPlayBaseBin * play_base_bin, GstPad * pad) name = g_strdup_printf ("preroll_%s", gst_pad_get_name (pad)); element = gst_element_factory_make ("queue", name); - g_object_set (G_OBJECT (element), "max-size-buffers", 0, NULL); - g_object_set (G_OBJECT (element), "max-size-bytes", 0, NULL); - g_object_set (G_OBJECT (element), "max-size-time", play_base_bin->queue_size, - NULL); - sig = - g_signal_connect (G_OBJECT (element), "overrun", + g_object_set (G_OBJECT (element), + "max-size-buffers", 0, "max-size-bytes", 10 * 1024 * 1024, + "max-size-time", play_base_bin->queue_size, NULL); + sig = g_signal_connect (G_OBJECT (element), "overrun", G_CALLBACK (queue_overrun), play_base_bin); /* keep a ref to the signal id so that we can disconnect the signal callback * when we are done with the preroll */ @@ -420,7 +419,7 @@ gen_preroll_element (GstPlayBaseBin * play_base_bin, GstPad * pad) static void remove_groups (GstPlayBaseBin * play_base_bin) { - GList *groups; + GList *groups, *item; /* first destroy the group we were building if any */ if (play_base_bin->building_group) { @@ -437,6 +436,13 @@ remove_groups (GstPlayBaseBin * play_base_bin) } g_list_free (play_base_bin->queued_groups); play_base_bin->queued_groups = NULL; + + /* clear subs */ + for (item = play_base_bin->subtitles; item; item = item->next) { + gst_bin_remove (GST_BIN (play_base_bin->thread), item->data); + } + g_list_free (play_base_bin->subtitles); + play_base_bin->subtitles = NULL; } /* Add/remove a single stream to current building group. @@ -457,6 +463,9 @@ add_stream (GstPlayBaseGroup * group, GstStreamInfo * info) case GST_STREAM_TYPE_VIDEO: group->nvideopads++; break; + case GST_STREAM_TYPE_TEXT: + group->ntextpads++; + break; default: group->nunknownpads++; break; @@ -684,6 +693,12 @@ new_decoded_pad (GstElement * element, GstPad * pad, gboolean last, if (group->nvideopads == 0) { need_preroll = TRUE; } + } else if (g_str_has_prefix (mimetype, "text/")) { + type = GST_STREAM_TYPE_TEXT; + /* first text pad gets a preroll element */ + if (group->ntextpads == 0) { + need_preroll = TRUE; + } } else { type = GST_STREAM_TYPE_UNKNOWN; } @@ -834,19 +849,66 @@ buffer_overrun (GstElement * queue, GstPlayBaseBin * play_base_bin) gst_play_base_bin_signals[BUFFERING_SIGNAL], 0, 100); } +/* + * Generate source ! subparse bins. + */ + +static GList * +setup_subtitles (GstPlayBaseBin * play_base_bin, gchar * sub_uri[]) +{ + GstElement *source, *subparse, *bin; + gint n; + gchar *name; + GList *subtitles = NULL; + + for (n = 0; sub_uri[n]; n++) { + source = gst_element_make_from_uri (GST_URI_SRC, sub_uri[n], NULL); + if (!source) + continue; + + subparse = gst_element_factory_make ("subparse", NULL); + name = g_strdup_printf ("subbin%d", n); + bin = gst_thread_new (name); + g_free (name); + + gst_bin_add_many (GST_BIN (bin), source, subparse, NULL); + gst_element_link (source, subparse); + gst_element_add_ghost_pad (bin, + gst_element_get_pad (subparse, "src"), "src"); + subtitles = g_list_append (subtitles, bin); + } + + return subtitles; +} + /* * Generate a source element that does caching for network streams. */ static GstElement * -gen_source_element (GstPlayBaseBin * play_base_bin) +gen_source_element (GstPlayBaseBin * play_base_bin, GList ** subbins) { GstElement *source, *queue, *bin; GstProbe *probe; gboolean is_stream; + gchar **src, **subs, *uri; - source = - gst_element_make_from_uri (GST_URI_SRC, play_base_bin->uri, "source"); + /* create subtitle elements */ + src = g_strsplit (play_base_bin->uri, "#", 2); + if (!src[0]) + return NULL; + if (src[1]) { + subs = g_strsplit (src[1], ",", 8); + *subbins = setup_subtitles (play_base_bin, subs); + g_strfreev (subs); + } else { + *subbins = NULL; + } + uri = src[0]; + src[0] = NULL; + g_strfreev (src); + + source = gst_element_make_from_uri (GST_URI_SRC, uri, "source"); if (!source) return NULL; @@ -889,6 +951,7 @@ setup_source (GstPlayBaseBin * play_base_bin, GError ** error) GstElement *old_src; GstElement *old_dec; GstPad *srcpad = NULL; + GList *new_subs, *item; if (!play_base_bin->need_rebuild) return TRUE; @@ -899,7 +962,7 @@ setup_source (GstPlayBaseBin * play_base_bin, GError ** error) old_src = play_base_bin->source; /* create and configure an element that can handle the uri */ - play_base_bin->source = gen_source_element (play_base_bin); + play_base_bin->source = gen_source_element (play_base_bin, &new_subs); if (!play_base_bin->source) { /* whoops, could not create the source element */ @@ -935,6 +998,20 @@ setup_source (GstPlayBaseBin * play_base_bin, GError ** error) /* remove our previous preroll queues */ remove_groups (play_base_bin); + /* do subs */ + if (new_subs) { + play_base_bin->subtitles = new_subs; + for (item = play_base_bin->subtitles; item; item = item->next) { + GstElement *bin = item->data; + + /* don't add yet, because we will preroll, and subs shouldn't + * preroll (we shouldn't preroll more than once source). */ + new_decoded_pad (bin, gst_element_get_pad (bin, "src"), FALSE, + play_base_bin); + gst_element_set_state (bin, GST_STATE_PAUSED); + } + } + /* now see if the source element emits raw audio/video all by itself, * if so, we can create streams for the pads and be done with it. * Also check that is has source pads, if not, we assume it will @@ -1056,6 +1133,11 @@ setup_source (GstPlayBaseBin * play_base_bin, GError ** error) play_base_bin->need_rebuild = FALSE; } + /* make subs iterate from now on */ + for (item = play_base_bin->subtitles; item; item = item->next) { + gst_bin_add (GST_BIN (play_base_bin->thread), item->data); + } + return TRUE; } diff --git a/gst/playback/gstplaybasebin.h b/gst/playback/gstplaybasebin.h index 658a5b8474..7c7fb4eb53 100644 --- a/gst/playback/gstplaybasebin.h +++ b/gst/playback/gstplaybasebin.h @@ -54,6 +54,7 @@ typedef struct gint naudiopads; gint nvideopads; + gint ntextpads; gint nunknownpads; GList *preroll_elems; @@ -71,6 +72,7 @@ struct _GstPlayBaseBin { gchar *uri; GstElement *source; GstElement *decoder; + GList *subtitles; /* additional filesrc ! subparse bins */ gboolean need_rebuild; /* group management */ diff --git a/gst/playback/gstplaybin.c b/gst/playback/gstplaybin.c index 8832308ea5..7c2e61d2b1 100644 --- a/gst/playback/gstplaybin.c +++ b/gst/playback/gstplaybin.c @@ -429,6 +429,44 @@ done: return element; } +/* make an element for playback of video with subtitles embedded. + * + * +--------------------------------------------------+ + * | tbin +-------------+ | + * | +-----+ | textoverlay | +------+ | + * | | csp | +--video_sink | | vbin | | + * video_sink-sink src+ +-text_sink src-sink | | + * | +-----+ | +-------------+ +------+ | + * text_sink-------------+ | + * +--------------------------------------------------+ + */ + +static GstElement * +gen_text_element (GstPlayBin * play_bin) +{ + GstElement *element, *csp, *overlay, *vbin; + + overlay = gst_element_factory_make ("textoverlay", "overlay"); + g_object_set (G_OBJECT (overlay), + "halign", "center", "valign", "bottom", NULL); + vbin = gen_video_element (play_bin); + if (!overlay) { + g_warning ("No overlay (pango) element, subtitles disabled"); + return vbin; + } + csp = gst_element_factory_make ("ffmpegcolorspace", "subtitlecsp"); + element = gst_bin_new ("textbin"); + gst_element_link_many (csp, overlay, vbin, NULL); + gst_bin_add_many (GST_BIN (element), csp, overlay, vbin, NULL); + + gst_element_add_ghost_pad (element, + gst_element_get_pad (overlay, "text_sink"), "text_sink"); + gst_element_add_ghost_pad (element, + gst_element_get_pad (csp, "sink"), "sink"); + + return element; +} + /* make the element (bin) that contains the elements needed to perform * audio playback. * @@ -520,7 +558,7 @@ done: * | | | | +-------------------+ | * | | +------+ | * sink-+ | - * +--------------------------------------------------------------------------+ + +--------------------------------------------------------------------------+ */ static GstElement * gen_vis_element (GstPlayBin * play_bin) @@ -641,7 +679,10 @@ setup_sinks (GstPlayBaseBin * play_base_bin) GList *s; gint num_audio = 0; gint num_video = 0; + gint num_text = 0; gboolean need_vis = FALSE; + gboolean need_text = FALSE; + GstPad *textsrcpad = NULL, *textsinkpad = NULL; /* FIXME: do this nicer, like taking a look at the installed * bins and figuring out if we can simply reconnect them, remove @@ -675,15 +716,20 @@ setup_sinks (GstPlayBaseBin * play_base_bin) num_audio++; } else if (type == 2) { num_video++; + } else if (type == 3) { + num_text++; } } /* no video, use vis */ if (num_video == 0 && num_audio > 0 && play_bin->visualisation) { need_vis = TRUE; + } else if (num_video > 0 && num_text > 0) { + need_text = TRUE; } num_audio = 0; num_video = 0; + num_text = 0; /* now actually connect everything */ for (s = streaminfo; s; s = g_list_next (s)) { @@ -727,9 +773,22 @@ setup_sinks (GstPlayBaseBin * play_base_bin) g_warning ("two video streams found, playing first one"); mute = TRUE; } else { - sink = gen_video_element (play_bin); + if (need_text) { + sink = gen_text_element (play_bin); + textsinkpad = gst_element_get_pad (sink, "text_sink"); + } else { + sink = gen_video_element (play_bin); + } num_video++; } + } else if (type == 3) { + if (num_text > 0) { + g_warning ("two subtitle streams found, playing first one"); + mute = TRUE; + } else { + textsrcpad = srcpad; + num_text++; + } } else if (type == 4) { /* we can ignore these streams here */ } else { @@ -769,6 +828,11 @@ setup_sinks (GstPlayBaseBin * play_base_bin) g_object_set (G_OBJECT (obj), "mute", TRUE, NULL); } } + + /* if subtitles, link */ + if (textsrcpad && num_video > 0) { + gst_pad_link (textsrcpad, textsinkpad); + } } static GstElementStateReturn diff --git a/gst/subparse/Makefile.am b/gst/subparse/Makefile.am new file mode 100644 index 0000000000..003f1a75fc --- /dev/null +++ b/gst/subparse/Makefile.am @@ -0,0 +1,11 @@ +plugin_LTLIBRARIES = libgstsubparse.la + +libgstsubparse_la_SOURCES = \ + gstsubparse.c + +libgstsubparse_la_CFLAGS = $(GST_CFLAGS) +libgstsubparse_la_LIBADD = +libgstsubparse_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +noinst_HEADERS = \ + gstsubparse.h diff --git a/gst/subparse/gstsubparse.c b/gst/subparse/gstsubparse.c new file mode 100644 index 0000000000..14aac548ac --- /dev/null +++ b/gst/subparse/gstsubparse.c @@ -0,0 +1,778 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (c) 2004 Ronald S. Bultje + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "gstsubparse.h" + +GST_DEBUG_CATEGORY_STATIC (subparse_debug); +#define GST_CAT_DEFAULT subparse_debug + +/* format enum */ +typedef enum +{ + GST_SUB_PARSE_FORMAT_UNKNOWN = 0, + GST_SUB_PARSE_FORMAT_MDVDSUB = 1, + GST_SUB_PARSE_FORMAT_SUBRIP = 2, + GST_SUB_PARSE_FORMAT_MPSUB = 3 +} GstSubParseFormat; + +static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-subtitle") + ); + +static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("text/plain; text/x-pango-markup") + ); + +static void gst_subparse_base_init (GstSubparseClass * klass); +static void gst_subparse_class_init (GstSubparseClass * klass); +static void gst_subparse_init (GstSubparse * subparse); + +static const GstFormat *gst_subparse_formats (GstPad * pad); +static const GstEventMask *gst_subparse_eventmask (GstPad * pad); +static gboolean gst_subparse_event (GstPad * pad, GstEvent * event); + +static GstElementStateReturn gst_subparse_change_state (GstElement * element); +static void gst_subparse_loop (GstElement * element); + +#if 0 +static GstCaps *gst_subparse_type_find (GstBuffer * buf, gpointer private); +#endif + +static GstElementClass *parent_class = NULL; + +GType +gst_subparse_get_type (void) +{ + static GType subparse_type = 0; + + if (!subparse_type) { + static const GTypeInfo subparse_info = { + sizeof (GstSubparseClass), + (GBaseInitFunc) gst_subparse_base_init, + NULL, + (GClassInitFunc) gst_subparse_class_init, + NULL, + NULL, + sizeof (GstSubparse), + 0, + (GInstanceInitFunc) gst_subparse_init, + }; + + subparse_type = g_type_register_static (GST_TYPE_ELEMENT, + "GstSubparse", &subparse_info, 0); + } + + return subparse_type; +} + +static void +gst_subparse_base_init (GstSubparseClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + static GstElementDetails subparse_details = { + "Subtitle parsers", + "Codec/Parser/Subtitle", + "Parses subtitle (.sub) files into text streams", + "Gustavo J. A. M. Carneiro \n" + "Ronald S. Bultje " + }; + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_templ)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_templ)); + gst_element_class_set_details (element_class, &subparse_details); +} + +static void +gst_subparse_class_init (GstSubparseClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + parent_class = g_type_class_ref (GST_TYPE_ELEMENT); + + element_class->change_state = gst_subparse_change_state; +} + +static void +gst_subparse_init (GstSubparse * subparse) +{ + subparse->sinkpad = + gst_pad_new_from_template (gst_static_pad_template_get (&sink_templ), + "sink"); + gst_element_add_pad (GST_ELEMENT (subparse), subparse->sinkpad); + + subparse->srcpad = + gst_pad_new_from_template (gst_static_pad_template_get (&src_templ), + "src"); + gst_pad_use_explicit_caps (subparse->srcpad); + gst_pad_set_formats_function (subparse->srcpad, gst_subparse_formats); + gst_pad_set_event_function (subparse->srcpad, gst_subparse_event); + gst_pad_set_event_mask_function (subparse->srcpad, gst_subparse_eventmask); + gst_element_add_pad (GST_ELEMENT (subparse), subparse->srcpad); + + gst_element_set_loop_function (GST_ELEMENT (subparse), gst_subparse_loop); + + subparse->textbuf = g_string_new (NULL); + subparse->parser.type = GST_SUB_PARSE_FORMAT_UNKNOWN; + subparse->parser_detected = FALSE; + subparse->seek_time = GST_CLOCK_TIME_NONE; + subparse->flush = FALSE; +} + +/* + * Source pad functions. + */ + +static const GstFormat * +gst_subparse_formats (GstPad * pad) +{ + static const GstFormat formats[] = { + GST_FORMAT_TIME, + 0 + }; + + return formats; +} + +static const GstEventMask * +gst_subparse_eventmask (GstPad * pad) +{ + static const GstEventMask masks[] = { + {GST_EVENT_SEEK, GST_SEEK_METHOD_SET}, + {0, 0} + }; + + return masks; +} + +static gboolean +gst_subparse_event (GstPad * pad, GstEvent * event) +{ + GstSubparse *self = GST_SUBPARSE (gst_pad_get_parent (pad)); + gboolean res = FALSE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + if (!(GST_EVENT_SEEK_FORMAT (event) == GST_FORMAT_TIME && + GST_EVENT_SEEK_METHOD (event) == GST_SEEK_METHOD_SET)) + break; + self->seek_time = GST_EVENT_SEEK_OFFSET (event); + res = TRUE; + break; + default: + break; + } + + gst_event_unref (event); + + return res; +} + +/* + * TRUE = continue, FALSE = stop. + */ + +static gboolean +gst_subparse_handle_event (GstSubparse * self, GstEvent * event) +{ + gboolean res = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_INTERRUPT: + gst_event_unref (event); + res = FALSE; + break; + case GST_EVENT_EOS: + res = FALSE; + /* fall-through */ + default: + gst_pad_event_default (self->sinkpad, event); + break; + } + + return res; +} + +static gchar * +convert_encoding (GstSubparse * self, const gchar * str, gsize len) +{ + gsize bytes_read, bytes_written; + gchar *rv; + GString *converted; + + converted = g_string_new (NULL); + while (len) { + GST_DEBUG ("Trying to convert '%s'", g_strndup (str, len)); + rv = g_locale_to_utf8 (str, len, &bytes_read, &bytes_written, NULL); + g_string_append_len (converted, rv, bytes_written); + len -= bytes_read; + str += bytes_read; + if (len) { + /* conversion error ocurred => skip one char */ + len--; + str++; + g_string_append_c (converted, '?'); + } + } + rv = converted->str; + g_string_free (converted, FALSE); + GST_DEBUG ("Converted to '%s'", rv); + return rv; +} + +static gchar * +get_next_line (GstSubparse * self) +{ + GstBuffer *buf; + const char *line_end; + int line_len; + gboolean have_r = FALSE; + gchar *line; + + if ((line_end = strchr (self->textbuf->str, '\n')) == NULL) { + /* end-of-line not found; try to get more data */ + buf = NULL; + do { + GstData *data = gst_pad_pull (self->sinkpad); + + if (GST_IS_EVENT (data)) { + if (!gst_subparse_handle_event (self, GST_EVENT (data))) + return NULL; + } else { + buf = GST_BUFFER (data); + } + } while (!buf); + self->textbuf = g_string_append_len (self->textbuf, + GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + gst_buffer_unref (buf); + /* search for end-of-line again */ + line_end = strchr (self->textbuf->str, '\n'); + } + /* get rid of '\r' */ + if ((int) (line_end - self->textbuf->str) > 0 && + self->textbuf->str[(int) (line_end - self->textbuf->str) - 1] == '\r') { + line_end--; + have_r = TRUE; + } + + if (line_end) { + line_len = line_end - self->textbuf->str; + line = convert_encoding (self, self->textbuf->str, line_len); + self->textbuf = g_string_erase (self->textbuf, 0, + line_len + (have_r ? 2 : 1)); + return line; + } + return NULL; +} + +static gchar * +parse_mdvdsub (GstSubparse * self, guint64 * out_start_time, + guint64 * out_end_time, gboolean after_seek) +{ + gchar *line, *line_start, *line_split, *line_chunk; + guint start_frame, end_frame; + + /* FIXME: hardcoded for now, but detecting the correct value is + * not going to be easy, I suspect... */ + const double frames_per_sec = 23.98; + GString *markup; + gchar *rv; + + /* style variables */ + gboolean italic; + gboolean bold; + guint fontsize; + + line = line_start = get_next_line (self); + if (!line) + return NULL; + + if (sscanf (line, "{%u}{%u}", &start_frame, &end_frame) != 2) { + g_warning ("Parse of the following line, assumed to be in microdvd .sub" + " format, failed:\n%s", line); + g_free (line_start); + return NULL; + } + *out_start_time = (start_frame - 1000) / frames_per_sec * GST_SECOND; + *out_end_time = (end_frame - 1000) / frames_per_sec * GST_SECOND; + /* skip the {%u}{%u} part */ + line = strchr (line, '}') + 1; + line = strchr (line, '}') + 1; + + markup = g_string_new (NULL); + while (1) { + italic = FALSE; + bold = FALSE; + fontsize = 0; + /* parse style markup */ + if (strncmp (line, "{y:i}", 5) == 0) { + italic = TRUE; + line = strchr (line, '}') + 1; + } + if (strncmp (line, "{y:b}", 5) == 0) { + bold = TRUE; + line = strchr (line, '}') + 1; + } + if (sscanf (line, "{s:%u}", &fontsize) == 1) { + line = strchr (line, '}') + 1; + } + if ((line_split = strchr (line, '|'))) + line_chunk = g_markup_escape_text (line, line_split - line); + else + line_chunk = g_markup_escape_text (line, strlen (line)); + markup = g_string_append (markup, "%s", line_chunk); + g_free (line_chunk); + if (line_split) { + g_string_append (markup, "\n"); + line = line_split + 1; + } else + break; + } + rv = markup->str; + g_string_free (markup, FALSE); + g_free (line_start); + GST_DEBUG ("parse_mdvdsub returning (start=%f, end=%f): %s", + *out_start_time / (double) GST_SECOND, + *out_end_time / (double) GST_SECOND, rv); + return rv; +} + +static void +parse_mdvdsub_init (GstSubparse * self) +{ + self->parser.deinit = NULL; + self->parser.parse = parse_mdvdsub; +} + +static gchar * +parse_subrip (GstSubparse * self, guint64 * out_start_time, + guint64 * out_end_time, gboolean after_seek) +{ + gchar *line; + guint h1, m1, s1, ms1; + guint h2, m2, s2, ms2; + int subnum; + + while (1) { + switch (self->state.subrip.state) { + case 0: + /* looking for a single integer */ + line = get_next_line (self); + if (!line) + return NULL; + if (sscanf (line, "%u", &subnum) == 1) + self->state.subrip.state = 1; + g_free (line); + break; + case 1: + /* looking for start_time --> end_time */ + line = get_next_line (self); + if (!line) + return NULL; + if (sscanf (line, "%u:%u:%u,%u --> %u:%u:%u,%u", + &h1, &m1, &s1, &ms1, &h2, &m2, &s2, &ms2) == 8) { + self->state.subrip.state = 2; + self->state.subrip.time1 = + (((guint64) h1) * 3600 + m1 * 60 + s1) * GST_SECOND + + ms1 * GST_MSECOND; + self->state.subrip.time2 = + (((guint64) h2) * 3600 + m2 * 60 + s2) * GST_SECOND + + ms2 * GST_MSECOND; + } else { + GST_DEBUG (0, "error parsing subrip time line"); + self->state.subrip.state = 0; + } + g_free (line); + break; + case 2: + /* looking for subtitle text; empty line ends this + * subtitle entry */ + line = get_next_line (self); + if (!line) + return NULL; + if (self->state.subrip.buf->len) + g_string_append_c (self->state.subrip.buf, '\n'); + g_string_append (self->state.subrip.buf, line); + if (strlen (line) == 0) { + gchar *rv; + + g_free (line); + *out_start_time = self->state.subrip.time1; + *out_end_time = self->state.subrip.time2; + rv = g_markup_escape_text (self->state.subrip.buf->str, + self->state.subrip.buf->len); + rv = g_strdup (self->state.subrip.buf->str); + g_string_truncate (self->state.subrip.buf, 0); + self->state.subrip.state = 0; + return rv; + } + g_free (line); + } + } +} + +static void +parse_subrip_deinit (GstSubparse * self) +{ + g_string_free (self->state.subrip.buf, TRUE); +} + +static void +parse_subrip_init (GstSubparse * self) +{ + self->state.subrip.state = 0; + self->state.subrip.buf = g_string_new (NULL); + self->parser.parse = parse_subrip; + self->parser.deinit = parse_subrip_deinit; +} + + +static gchar * +parse_mpsub (GstSubparse * self, guint64 * out_start_time, + guint64 * out_end_time, gboolean after_seek) +{ + gchar *line; + float t1, t2; + + if (after_seek) { + self->state.mpsub.time = 0; + } + + while (1) { + switch (self->state.mpsub.state) { + case 0: + /* looking for two floats (offset, duration) */ + line = get_next_line (self); + if (!line) + return NULL; + if (sscanf (line, "%f %f", &t1, &t2) == 2) { + self->state.mpsub.state = 1; + self->state.mpsub.time += GST_SECOND * t1; + } + g_free (line); + break; + case 1: + /* looking for subtitle text; empty line ends this + * subtitle entry */ + line = get_next_line (self); + if (!line) + return NULL; + if (self->state.mpsub.buf->len) + g_string_append_c (self->state.mpsub.buf, '\n'); + g_string_append (self->state.mpsub.buf, line); + if (strlen (line) == 0) { + gchar *rv; + + g_free (line); + *out_start_time = self->state.mpsub.time; + *out_end_time = self->state.mpsub.time + t2 * GST_SECOND; + self->state.mpsub.time += t2 * GST_SECOND; + rv = g_markup_escape_text (self->state.mpsub.buf->str, + self->state.mpsub.buf->len); + rv = g_strdup (self->state.mpsub.buf->str); + g_string_truncate (self->state.mpsub.buf, 0); + self->state.mpsub.state = 0; + return rv; + } + g_free (line); + break; + } + } + + return NULL; +} + +static void +parse_mpsub_deinit (GstSubparse * self) +{ + g_string_free (self->state.mpsub.buf, TRUE); +} + +static void +parse_mpsub_init (GstSubparse * self) +{ + self->state.mpsub.state = 0; + self->state.mpsub.buf = g_string_new (NULL); + self->parser.deinit = parse_mpsub_deinit; + self->parser.parse = parse_mpsub; +} + +/* + * FIXME: maybe we should pass along a second argument, the preceding + * text buffer, because that is how this originally worked, even though + * I don't really see the use of that. + */ + +static GstSubParseFormat +gst_subparse_buffer_format_autodetect (GstBuffer * buf) +{ + static gboolean need_init_regexps = TRUE; + static regex_t mdvd_rx; + static regex_t subrip_rx; + const gchar *str = GST_BUFFER_DATA (buf); + + /* initialize the regexps used the first time around */ + if (need_init_regexps) { + int err; + char errstr[128]; + + need_init_regexps = FALSE; + regcomp (&mdvd_rx, "^\\{[0-9]+\\}\\{[0-9]+\\}", + REG_EXTENDED | REG_NEWLINE | REG_NOSUB); + if ((err = regcomp (&subrip_rx, "^1\x0d\x0a" + "[0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9]{3}" + " --> [0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9]{3}", + REG_EXTENDED | REG_NEWLINE | REG_NOSUB)) != 0) { + regerror (err, &subrip_rx, errstr, 127); + GST_WARNING ("Compilation of subrip regex failed: %s", errstr); + } + } + + if (regexec (&mdvd_rx, str, 0, NULL, 0) == 0) { + GST_LOG ("subparse: MicroDVD (frame based) format detected"); + return GST_SUB_PARSE_FORMAT_MDVDSUB; + } + if (regexec (&subrip_rx, str, 0, NULL, 0) == 0) { + GST_LOG ("subparse: SubRip (time based) format detected"); + return GST_SUB_PARSE_FORMAT_SUBRIP; + } + if (!strncmp (str, "FORMAT=TIME", 11)) { + GST_LOG ("subparse: MPSub (time based) format detected"); + return GST_SUB_PARSE_FORMAT_MPSUB; + } + GST_WARNING ("subparse: subtitle format autodetection failed!"); + return GST_SUB_PARSE_FORMAT_UNKNOWN; +} + +static gboolean +gst_subparse_format_autodetect (GstSubparse * self) +{ + GstBuffer *buf = NULL; + GstSubParseFormat format; + gboolean res = TRUE; + + do { + GstData *data = gst_pad_pull (self->sinkpad); + + if (GST_IS_EVENT (data)) { + if (!gst_subparse_handle_event (self, GST_EVENT (data))) + return FALSE; + } else { + buf = GST_BUFFER (data); + } + } while (!buf); + self->textbuf = g_string_append_len (self->textbuf, GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf)); + format = gst_subparse_buffer_format_autodetect (buf); + gst_buffer_unref (buf); + self->parser_detected = TRUE; + self->parser.type = format; + switch (format) { + case GST_SUB_PARSE_FORMAT_MDVDSUB: + GST_DEBUG ("MicroDVD format detected"); + parse_mdvdsub_init (self); + res = gst_pad_set_explicit_caps (self->srcpad, + gst_caps_new_simple ("text/x-pango-markup", NULL)); + break; + case GST_SUB_PARSE_FORMAT_SUBRIP: + GST_DEBUG ("SubRip format detected"); + parse_subrip_init (self); + res = gst_pad_set_explicit_caps (self->srcpad, + gst_caps_new_simple ("text/plain", NULL)); + break; + case GST_SUB_PARSE_FORMAT_MPSUB: + GST_DEBUG ("MPSub format detected"); + parse_mpsub_init (self); + res = gst_pad_set_explicit_caps (self->srcpad, + gst_caps_new_simple ("text/plain", NULL)); + break; + case GST_SUB_PARSE_FORMAT_UNKNOWN: + default: + GST_DEBUG ("no subtitle format detected"); + GST_ELEMENT_ERROR (self, STREAM, WRONG_TYPE, + ("The input is not a valid/supported subtitle file"), (NULL)); + res = FALSE; + break; + } + + return res; +} + +/* + * parse input, getting a start and end time + * then parse next input, and if next start time > current end time, send + * clear buffer. + */ + +static void +gst_subparse_loop (GstElement * element) +{ + GstSubparse *self; + GstBuffer *buf; + guint64 start_time, end_time, need_time = GST_CLOCK_TIME_NONE; + gchar *subtitle; + gboolean after_seek = FALSE; + + GST_DEBUG ("gst_subparse_loop"); + self = GST_SUBPARSE (element); + + /* make sure we know the format */ + if (!self->parser_detected) { + if (!gst_subparse_format_autodetect (self)) + return; + } + + /* handle seeks */ + if (GST_CLOCK_TIME_IS_VALID (self->seek_time)) { + GstEvent *seek; + + seek = gst_event_new_seek (GST_SEEK_FLAG_FLUSH | GST_FORMAT_BYTES | + GST_SEEK_METHOD_SET, 0); + if (gst_pad_send_event (GST_PAD_PEER (self->sinkpad), seek)) { + need_time = self->seek_time; + after_seek = TRUE; + + if (self->flush) { + gst_pad_push (self->srcpad, GST_DATA (gst_event_new (GST_EVENT_FLUSH))); + self->flush = FALSE; + } + gst_pad_push (self->srcpad, + GST_DATA (gst_event_new_discontinuous (FALSE, + GST_FORMAT_TIME, need_time, GST_FORMAT_UNDEFINED))); + } + + self->seek_time = GST_CLOCK_TIME_NONE; + } + + /* get a next buffer */ + GST_INFO ("getting text buffer"); + if (!self->parser.parse || self->parser.type == GST_SUB_PARSE_FORMAT_UNKNOWN) { + GST_ELEMENT_ERROR (self, LIBRARY, INIT, (NULL), (NULL)); + return; + } + + do { + subtitle = self->parser.parse (self, &start_time, &end_time, after_seek); + if (!subtitle) + return; + after_seek = FALSE; + + if (GST_CLOCK_TIME_IS_VALID (need_time) && end_time < need_time) { + g_free (subtitle); + } else { + need_time = GST_CLOCK_TIME_NONE; + GST_DEBUG ("subparse: loop: text %s, start %lld, end %lld\n", + subtitle, start_time, end_time); + + buf = gst_buffer_new (); + GST_BUFFER_DATA (buf) = subtitle; + GST_BUFFER_SIZE (buf) = strlen (subtitle); + GST_BUFFER_TIMESTAMP (buf) = start_time; + GST_BUFFER_DURATION (buf) = end_time - start_time; + GST_DEBUG ("sending text buffer %s at %lld", subtitle, start_time); + gst_pad_push (self->srcpad, GST_DATA (buf)); + } + } while (GST_CLOCK_TIME_IS_VALID (need_time)); +} + +static GstElementStateReturn +gst_subparse_change_state (GstElement * element) +{ + GstSubparse *self = GST_SUBPARSE (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_PAUSED_TO_READY: + self->parser.deinit (self); + self->parser.type = GST_SUB_PARSE_FORMAT_UNKNOWN; + self->parser_detected = FALSE; + self->seek_time = GST_CLOCK_TIME_NONE; + self->flush = FALSE; + break; + default: + break; + } + + return parent_class->change_state (element); +} + +#if 0 +/* typefinding stuff */ +static GstTypeDefinition subparse_definition = { + "subparse/x-text", + "text/plain", + ".sub", + gst_subparse_type_find, +}; +static GstCaps * +gst_subparse_type_find (GstBuffer * buf, gpointer private) +{ + GstSubParseFormat format; + + format = gst_subparse_buffer_format_autodetect (buf); + switch (format) { + case GST_SUB_PARSE_FORMAT_MDVDSUB: + GST_DEBUG (GST_CAT_PLUGIN_INFO, "MicroDVD format detected"); + return gst_caps_new ("subparse_type_find", "text/plain", NULL); + case GST_SUB_PARSE_FORMAT_SUBRIP: + GST_DEBUG (GST_CAT_PLUGIN_INFO, "SubRip format detected"); + return gst_caps_new ("subparse_type_find", "text/plain", NULL); + case GST_SUB_PARSE_FORMAT_UNKNOWN: + GST_DEBUG (GST_CAT_PLUGIN_INFO, "no subtitle format detected"); + break; + } + /* don't know which this is */ + return NULL; +} +#endif + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (subparse_debug, "subparse", 0, ".sub parser"); + + return gst_element_register (plugin, "subparse", + GST_RANK_PRIMARY, GST_TYPE_SUBPARSE); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "subparse", + "Subtitle (.sub) file parsing", + plugin_init, VERSION, "LGPL", GST_PACKAGE, GST_ORIGIN) diff --git a/gst/subparse/gstsubparse.h b/gst/subparse/gstsubparse.h new file mode 100644 index 0000000000..88bedfb399 --- /dev/null +++ b/gst/subparse/gstsubparse.h @@ -0,0 +1,87 @@ +/* GStreamer + * Copyright (C) <2002> David A. Schleef + * Copyright (C) <1999> Erik Walthinsen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_SUBPARSE_H__ +#define __GST_SUBPARSE_H__ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_SUBPARSE \ + (gst_subparse_get_type ()) +#define GST_SUBPARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_SUBPARSE, GstSubparse)) +#define GST_SUBPARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_SUBPARSE, GstSubparse)) +#define GST_IS_SUBPARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_SUBPARSE)) +#define GST_IS_SUBPARSE_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_SUBPARSE)) + +typedef struct _GstSubparse GstSubparse; +typedef struct _GstSubparseClass GstSubparseClass; + +typedef void (* GstSubparseInit) (GstSubparse *self); +typedef gchar * (* GstSubparseParser) (GstSubparse *self, + guint64 *out_start_time, + guint64 *out_end_time, + gboolean after_seek); + +struct _GstSubparse { + GstElement element; + + GstPad *sinkpad,*srcpad; + + GString *textbuf; + struct { + GstSubparseInit deinit; + GstSubparseParser parse; + gint type; + } parser; + gboolean parser_detected; + + union { + struct { + int state; + GString *buf; + guint64 time1, time2; + } subrip; + struct { + int state; + GString *buf; + guint64 time; + } mpsub; + } state; + + /* seek */ + guint64 seek_time; + gboolean flush; +}; + +struct _GstSubparseClass { + GstElementClass parent_class; +}; + +GType gst_subparse_get_type (void); + +G_END_DECLS + +#endif /* __GST_SUBPARSE_H__ */ diff --git a/gst/typefind/gsttypefindfunctions.c b/gst/typefind/gsttypefindfunctions.c index 054ed293df..8a48cd6a5f 100644 --- a/gst/typefind/gsttypefindfunctions.c +++ b/gst/typefind/gsttypefindfunctions.c @@ -1348,6 +1348,21 @@ ogmaudio_type_find (GstTypeFind * tf, gpointer private) } } +static GstStaticCaps ogmtext_caps = GST_STATIC_CAPS ("application/x-ogm-text"); + +#define OGMTEXT_CAPS (gst_static_caps_get(&ogmtext_caps)) +static void +ogmtext_type_find (GstTypeFind * tf, gpointer private) +{ + guint8 *data = gst_type_find_peek (tf, 0, 9); + + if (data) { + if (memcmp (data, "\001text\000\000\000\000", 9) != 0) + return; + gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, OGMTEXT_CAPS); + } +} + /*** audio/x-speex ***********************************************************/ static GstStaticCaps speex_caps = GST_STATIC_CAPS ("audio/x-speex"); @@ -1615,6 +1630,8 @@ plugin_init (GstPlugin * plugin) ogmvideo_type_find, NULL, OGMVIDEO_CAPS, NULL); TYPE_FIND_REGISTER (plugin, "application/x-ogm-audio", GST_RANK_PRIMARY, ogmaudio_type_find, NULL, OGMAUDIO_CAPS, NULL); + TYPE_FIND_REGISTER (plugin, "application/x-ogm-text", GST_RANK_PRIMARY, + ogmtext_type_find, NULL, OGMTEXT_CAPS, NULL); TYPE_FIND_REGISTER (plugin, "audio/x-speex", GST_RANK_PRIMARY, speex_type_find, NULL, SPEEX_CAPS, NULL); TYPE_FIND_REGISTER (plugin, "audio/x-m4a", GST_RANK_PRIMARY, m4a_type_find,