OGM text support, Matroska UTF-8 text support, deadlock fixes all over the place...
authorRonald S. Bultje <rbultje@ronald.bitfreak.net>
Sat, 8 Jan 2005 18:22:41 +0000 (18:22 +0000)
committerRonald S. Bultje <rbultje@ronald.bitfreak.net>
Sat, 8 Jan 2005 18:22:41 +0000 (18:22 +0000)
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.

14 files changed:
ChangeLog
configure.ac
ext/ogg/gstoggdemux.c
ext/ogg/gstogmparse.c
ext/pango/gsttextoverlay.c
ext/pango/gsttextoverlay.h
gst/playback/gstdecodebin.c
gst/playback/gstplaybasebin.c
gst/playback/gstplaybasebin.h
gst/playback/gstplaybin.c
gst/subparse/Makefile.am [new file with mode: 0644]
gst/subparse/gstsubparse.c [new file with mode: 0644]
gst/subparse/gstsubparse.h [new file with mode: 0644]
gst/typefind/gsttypefindfunctions.c

index 90155d2..4f3187e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,55 @@
 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):
          Check for pad availability before doing a query on it.
 
index f730ddb..b50d45f 100644 (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
index dd8a701..fc22cba 100644 (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;
index 44dddc8..d8f257e 100644 (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)
 {
@@ -258,6 +298,26 @@ gst_ogm_video_parse_base_init (GstOgmParseClass * klass)
 }
 
 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)
 {
   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (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);
 }
index 52f257a..bbd92b0 100644 (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 GList *
+gst_textoverlay_linkedpads (GstPad * pad)
+{
+  GstPad *otherpad;
+  GstTextOverlay *overlay;
+
+  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_video_sinkconnect (GstPad * pad, const GstCaps * caps)
+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;
-
-    if (PAST_END (overlay->next_buffer, now)) {
-      gst_buffer_unref (overlay->next_buffer);
-      overlay->next_buffer = NULL;
+    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);
+
+        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_data = overlay->next_data;
+    overlay->next_data = NULL;
+
+    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);
     }
-    overlay->current_buffer = overlay->next_buffer;
-    overlay->next_buffer = 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));
     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;
 }
 
index df5b5b5..669f88d 100644 (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;
 };
index 508cc16..faf4d2c 100644 (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;
 
index faa166c..76fe31a 100644 (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;
   }
@@ -835,18 +850,65 @@ buffer_overrun (GstElement * queue, GstPlayBaseBin * play_base_bin)
 }
 
 /*
+ * 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;
+
+  /* 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, play_base_bin->uri, "source");
+  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;
 }
 
index 658a5b8..7c7fb4e 100644 (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 */
index 8832308..7c2e61d 100644 (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
diff --git a/gst/subparse/Makefile.am b/gst/subparse/Makefile.am
new file mode 100644 (file)
index 0000000..003f1a7
--- /dev/null
@@ -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 (file)
index 0000000..14aac54
--- /dev/null
@@ -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)
diff --git a/gst/subparse/gstsubparse.h b/gst/subparse/gstsubparse.h
new file mode 100644 (file)
index 0000000..88bedfb
--- /dev/null
@@ -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__ */
index 054ed29..8a48cd6 100644 (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,