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.
This commit is contained in:
Ronald S. Bultje 2005-01-08 18:22:41 +00:00
parent e59bf40bb1
commit 18881b687a
14 changed files with 1503 additions and 80 deletions

View file

@ -1,3 +1,53 @@
2005-01-08 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
* 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 <rbultje@ronald.bitfreak.net>
* ext/vorbis/vorbisdec.c: (vorbis_dec_src_query):

View file

@ -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

View file

@ -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;

View file

@ -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 <rbultje@ronald.bitfreak.net>");
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);
}

View file

@ -24,6 +24,9 @@
#include <gst/gst.h>
#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;
}

View file

@ -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;
};

View file

@ -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;

View file

@ -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;
}

View file

@ -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 */

View file

@ -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

11
gst/subparse/Makefile.am Normal file
View file

@ -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

778
gst/subparse/gstsubparse.c Normal file
View file

@ -0,0 +1,778 @@
/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (c) 2004 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
*
* 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 <string.h>
#include <stdlib.h>
#include <regex.h>
#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 <gjc@inescporto.pt>\n"
"Ronald S. Bultje <rbultje@ronald.bitfreak.net>"
};
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, "<span");
if (italic)
g_string_append (markup, " style=\"italic\"");
if (bold)
g_string_append (markup, " weight=\"bold\"");
if (fontsize)
g_string_append_printf (markup, " size=\"%u\"", fontsize * 1000);
g_string_append_printf (markup, ">%s</span>", 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)

View file

@ -0,0 +1,87 @@
/* GStreamer
* Copyright (C) <2002> David A. Schleef <ds@schleef.org>
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
*
* 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 <gst/gst.h>
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__ */

View file

@ -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,