Index: gst/subparse/gstsubparse.c
===================================================================
RCS file: /cvs/gstreamer/gst-plugins-base/gst/subparse/gstsubparse.c,v
retrieving revision 1.28
diff -u -r1.28 gstsubparse.c
--- gst/subparse/gstsubparse.c	8 Apr 2006 21:02:51 -0000	1.28
+++ gst/subparse/gstsubparse.c	26 Apr 2006 16:06:53 -0000
@@ -26,6 +26,9 @@
 #include <stdlib.h>
 #include <sys/types.h>
 #include <regex.h>
+#ifndef GST_DISABLE_LOADSAVE_REGISTRY
+#include <libxml/HTMLparser.h>
+#endif
 
 #include "gstsubparse.h"
 #include "gstssaparse.h"
@@ -64,6 +67,10 @@
 
 static GstFlowReturn gst_sub_parse_chain (GstPad * sinkpad, GstBuffer * buf);
 
+#ifndef GST_DISABLE_LOADSAVE_REGISTRY
+static void sami_context_deinit (ParserState * state);
+#endif
+
 static GstElementClass *parent_class = NULL;
 
 GType
@@ -114,6 +121,9 @@
     gst_segment_free (subparse->segment);
     subparse->segment = NULL;
   }
+#ifndef GST_DISABLE_LOADSAVE_REGISTRY
+  sami_context_deinit (&subparse->state);
+#endif
 
   GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
 }
@@ -545,6 +555,332 @@
   }
 }
 
+#ifndef GST_DISABLE_LOADSAVE_REGISTRY
+
+#define ITALIC_TAG 'i'
+#define COLOR_TAG 'c'
+#define RUBY_TAG 'r'
+#define RT_TAG 't'
+#define CLEAR_TAG '0'
+
+typedef struct
+{
+  GString *buf, *rubybuf;
+  GString *state;
+  htmlParserCtxtPtr htmlctxt;
+  gboolean has_result;
+  gboolean in_title;
+  guint64 time1, time2;
+} samiContext;
+
+static gchar *
+has_tag (GString * str, const gchar tag)
+{
+  return strrchr (str->str, tag);
+}
+
+static void
+sami_context_push_state (samiContext * sctx, char state)
+{
+  g_string_append_c (sctx->state, state);
+}
+
+static void
+sami_context_pop_state (samiContext * sctx, char state)
+{
+  GString *str = g_string_new ("");
+  GString *context_state = sctx->state;
+  int i;
+
+  for (i = context_state->len - 1; i >= 0; i--) {
+    switch (context_state->str[i]) {
+      case ITALIC_TAG:         /* <i> */
+      {
+        g_string_append (str, "</i>");
+        break;
+      }
+      case COLOR_TAG:          /* <span foreground= > */
+      {
+        g_string_append (str, "</span>");
+        break;
+      }
+      case RUBY_TAG:           /* <span size= >  -- ruby */
+      {
+        break;
+      }
+      case RT_TAG:             /*  ruby */
+      {
+        /* FIXME */
+        g_string_append (sctx->rubybuf, "</span>");
+        if (has_tag (context_state, ITALIC_TAG)) {
+          g_string_append (sctx->rubybuf, "</i>");
+        }
+
+        break;
+      }
+      default:
+        break;
+    }
+    if (context_state->str[i] == state) {
+      g_string_append (sctx->buf, str->str);
+      g_string_free (str, TRUE);
+      g_string_truncate (context_state, i);
+      return;
+    }
+  }
+  if (state == CLEAR_TAG) {
+    g_string_append (sctx->buf, str->str);
+    g_string_truncate (context_state, 0);
+  }
+  g_string_free (str, TRUE);
+}
+
+static void
+handle_start_sync (samiContext * sctx, const xmlChar ** atts)
+{
+  int i;
+
+  sami_context_pop_state (sctx, CLEAR_TAG);
+  if (atts != NULL) {
+    for (i = 0; (atts[i] != NULL); i += 2) {
+      const xmlChar *key, *value;
+
+      key = atts[i];
+      value = atts[i + 1];
+
+      if (!value)
+        continue;
+      if (!xmlStrncmp ((const xmlChar *) "start", key, 5)) {
+        sctx->time1 = sctx->time2;
+        sctx->time2 = atoi ((const char *) value) * GST_MSECOND;
+        sctx->has_result = TRUE;
+      }
+    }
+  }
+}
+
+static void
+handle_start_font (samiContext * sctx, const xmlChar ** atts)
+{
+  int i;
+
+  sami_context_pop_state (sctx, COLOR_TAG);
+  if (atts != NULL) {
+    for (i = 0; (atts[i] != NULL); i += 2) {
+      const xmlChar *key, *value;
+
+      key = atts[i];
+      value = atts[i + 1];
+
+      if (!value)
+        continue;
+      if (!xmlStrncmp ((const xmlChar *) "color", key, 5)) {
+        /*
+         * There are invalid color value in many
+         * sami files.
+         * It will fix hex color value that start without '#'
+         */
+        gchar *sharp = "";
+        int len = xmlStrlen (value);
+
+        if (!(*value == '#' && len == 7)) {
+          gchar *r;
+
+          strtol ((const char *) value, &r, 16);        /* trying onvert hex */
+          if (((xmlChar *) r == (value + 6) && len == 6)) {
+            sharp = "#";
+          }
+        }
+        g_string_append_printf (sctx->buf, "<span foreground=\"%s%s\">", sharp,
+            value);
+        sami_context_push_state (sctx, COLOR_TAG);
+      }
+    }
+  }
+}
+
+static void
+start_sami_element (void *ctx, const xmlChar * name, const xmlChar ** atts)
+{
+  samiContext *sctx = (samiContext *) ctx;
+
+  if (!xmlStrncmp ((const xmlChar *) "title", name, 5)) {
+    sctx->in_title = TRUE;
+  } else if (!xmlStrncmp ((const xmlChar *) "sync", name, 4)) {
+    handle_start_sync (sctx, atts);
+  } else if (!xmlStrncmp ((const xmlChar *) "font", name, 4)) {
+    handle_start_font (sctx, atts);
+  } else if (!xmlStrncmp ((const xmlChar *) "ruby", name, 4)) {
+    sami_context_push_state (sctx, RUBY_TAG);
+  } else if (!xmlStrncmp ((const xmlChar *) "br", name, 2)) {
+    g_string_append_c (sctx->buf, '\n');
+    /* FIXME */
+  } else if (!xmlStrncmp ((const xmlChar *) "rt", name, 2)) {
+    if (has_tag (sctx->state, ITALIC_TAG)) {
+      g_string_append (sctx->rubybuf, "<i>");
+    }
+    g_string_append (sctx->rubybuf, "<span size='xx-small' rise='-100'>");
+    sami_context_push_state (sctx, RT_TAG);
+  } else if (!xmlStrncmp ((const xmlChar *) "p", name, 1)) {
+  } else if (!xmlStrncmp ((const xmlChar *) "i", name, 1)) {
+    g_string_append (sctx->buf, "<i>");
+    sami_context_push_state (sctx, ITALIC_TAG);
+  }
+}
+
+static void
+end_sami_element (void *ctx, const xmlChar * name)
+{
+  samiContext *sctx = (samiContext *) ctx;
+
+  if (!xmlStrncmp ((const xmlChar *) "title", name, 5)) {
+    sctx->in_title = FALSE;
+  } else if (!xmlStrncmp ((const xmlChar *) "font", name, 4)) {
+    sami_context_pop_state (sctx, COLOR_TAG);
+  } else if (!xmlStrncmp ((const xmlChar *) "ruby", name, 4)) {
+    sami_context_pop_state (sctx, RUBY_TAG);
+  } else if (!xmlStrncmp ((const xmlChar *) "i", name, 1)) {
+    sami_context_pop_state (sctx, ITALIC_TAG);
+  }
+}
+
+static void
+characters_sami (void *ctx, const xmlChar * ch, int len)
+{
+  samiContext *sctx = (samiContext *) ctx;
+
+  /* skip title */
+  if (sctx->in_title)
+    return;
+
+  if (has_tag (sctx->state, RT_TAG)) {
+    g_string_append_c (sctx->rubybuf, ' ');
+    g_string_append_len (sctx->rubybuf, (const gchar *) ch, len);
+    g_string_append_c (sctx->rubybuf, ' ');
+  } else {
+    g_string_append_len (sctx->buf, (const gchar *) ch, len);
+  }
+}
+
+static xmlSAXHandler samiSAXHandlerStruct = {
+  NULL,                         /* internalSubset */
+  NULL,                         /* isStandalone */
+  NULL,                         /* hasInternalSubset */
+  NULL,                         /* hasExternalSubset */
+  NULL,                         /* resolveEntity */
+  NULL,                         /* getEntity */
+  NULL,                         /* entityDecl */
+  NULL,                         /* notationDecl */
+  NULL,                         /* attributeDecl */
+  NULL,                         /* elementDecl */
+  NULL,                         /* unparsedEntityDecl */
+  NULL,                         /* setDocumentLocator */
+  NULL,                         /* startDocument */
+  NULL,                         /* endDocument */
+  start_sami_element,           /* startElement */
+  end_sami_element,             /* endElement */
+  NULL,                         /* reference */
+  characters_sami,              /* characters */
+  NULL,                         /* ignorableWhitespace */
+  NULL,                         /* processingInstruction */
+  NULL,                         /* comment */
+  NULL,                         /* xmlParserWarning */
+  NULL,                         /* xmlParserError */
+  NULL,                         /* xmlParserError */
+  NULL,                         /* getParameterEntity */
+  NULL,                         /* cdataBlock */
+  NULL,                         /* externalSubset */
+  1,                            /* initialized */
+  NULL,                         /* private */
+  NULL,                         /* startElementNsSAX2Func */
+  NULL,                         /* endElementNsSAX2Func */
+  NULL                          /* xmlStructuredErrorFunc */
+};
+static xmlSAXHandlerPtr samiSAXHandler = &samiSAXHandlerStruct;
+
+static void
+sami_context_init (ParserState * state)
+{
+  samiContext *context;
+
+  g_assert (state->user_data == NULL);
+  state->user_data = (void *) g_new0 (samiContext, 1);
+  context = (samiContext *) state->user_data;
+
+  context->htmlctxt = htmlCreatePushParserCtxt (samiSAXHandler, context,
+      "", 0, NULL, XML_CHAR_ENCODING_UTF8);
+  context->buf = g_string_new ("");
+  context->rubybuf = g_string_new ("");
+  context->state = g_string_new ("");
+}
+
+static void
+sami_context_deinit (ParserState * state)
+{
+  samiContext *context = (samiContext *) state->user_data;
+
+  if (context) {
+    htmlParserCtxtPtr htmlctxt = context->htmlctxt;
+
+    /* destroy sax context */
+    htmlDocPtr doc;
+
+    htmlParseChunk (htmlctxt, "", 0, 1);
+    doc = htmlctxt->myDoc;
+    htmlFreeParserCtxt (htmlctxt);
+    context->htmlctxt = NULL;
+    if (doc)
+      xmlFreeDoc (doc);
+    g_string_free (context->buf, TRUE);
+    g_string_free (context->rubybuf, TRUE);
+    g_string_free (context->state, TRUE);
+    g_free (context);
+    state->user_data = NULL;
+  }
+}
+
+static void
+sami_context_clean (ParserState * state)
+{
+  samiContext *context = (samiContext *) state->user_data;
+
+  if (context) {
+    g_string_truncate (context->buf, 0);
+    g_string_truncate (context->rubybuf, 0);
+    g_string_truncate (context->state, 0);
+    context->has_result = FALSE;
+    context->in_title = FALSE;
+    context->time1 = context->time2 = 0;
+  }
+}
+
+
+static gchar *
+parse_sami (ParserState * state, const gchar * line)
+{
+  samiContext *context = (samiContext *) state->user_data;
+
+  htmlParseChunk (context->htmlctxt, line, strlen (line), 0);
+  if (context->has_result) {
+    gchar *r;
+
+    if (context->rubybuf->len) {
+      context->rubybuf = g_string_append_c (context->rubybuf, '\n');
+      g_string_prepend (context->buf, context->rubybuf->str);
+      context->rubybuf = g_string_truncate (context->rubybuf, 0);
+    }
+
+    r = g_string_free (context->buf, FALSE);
+    context->buf = g_string_new ("");
+    state->start_time = context->time1;
+    state->duration = context->time2 - context->time1;
+    context->has_result = FALSE;
+    return r;
+  }
+  return NULL;
+}
+#endif
+
 static void
 parser_state_init (ParserState * state)
 {
@@ -569,6 +905,11 @@
     g_string_free (state->buf, TRUE);
     state->buf = NULL;
   }
+#ifndef GST_DISABLE_LOADSAVE_REGISTRY
+  if (state->user_data) {
+    sami_context_clean (state);
+  }
+#endif
 }
 
 /*
@@ -613,7 +954,12 @@
     GST_LOG ("subparse: MPSub (time based) format detected");
     return GST_SUB_PARSE_FORMAT_MPSUB;
   }
-
+#ifndef GST_DISABLE_LOADSAVE_REGISTRY
+  if (xmlStrcasestr ((const xmlChar *) match_str, (xmlChar *) "<SAMI>")) {
+    GST_LOG ("subparse: SAMI (time based) format detected");
+    return GST_SUB_PARSE_FORMAT_SAMI;
+  }
+#endif
   GST_WARNING ("subparse: subtitle format autodetection failed!");
   return GST_SUB_PARSE_FORMAT_UNKNOWN;
 }
@@ -646,6 +992,12 @@
     case GST_SUB_PARSE_FORMAT_MPSUB:
       self->parse_line = parse_mpsub;
       return gst_caps_new_simple ("text/plain", NULL);
+#ifndef GST_DISABLE_LOADSAVE_REGISTRY
+    case GST_SUB_PARSE_FORMAT_SAMI:
+      self->parse_line = parse_sami;
+      sami_context_init (&self->state);
+      return gst_caps_new_simple ("text/x-pango-markup", NULL);
+#endif
     case GST_SUB_PARSE_FORMAT_UNKNOWN:
     default:
       GST_DEBUG ("no subtitle format detected");
@@ -662,6 +1014,9 @@
     /* flush the parser state */
     parser_state_init (&self->state);
     g_string_truncate (self->textbuf, 0);
+#ifndef GST_DISABLE_LOADSAVE_REGISTRY
+    sami_context_clean (&self->state);
+#endif
   }
 
   self->textbuf = g_string_append_len (self->textbuf,
@@ -895,6 +1250,11 @@
     case GST_SUB_PARSE_FORMAT_MPSUB:
       GST_DEBUG ("MPSub format detected");
       break;
+#ifndef GST_DISABLE_LOADSAVE_REGISTRY
+    case GST_SUB_PARSE_FORMAT_SAMI:
+      GST_DEBUG ("SAMI format detected");
+      break;
+#endif
     case GST_SUB_PARSE_FORMAT_UNKNOWN:
       GST_DEBUG ("no subtitle format detected");
       return;
@@ -907,7 +1267,11 @@
 static gboolean
 plugin_init (GstPlugin * plugin)
 {
+#ifdef GST_DISABLE_LOADSAVE_REGISTRY
   static gchar *sub_exts[] = { "srt", "sub", "mpsub", "mdvd", NULL };
+#else
+  static gchar *sub_exts[] = { "srt", "sub", "mpsub", "mdvd", "smi", NULL };
+#endif
 
   GST_DEBUG_CATEGORY_INIT (sub_parse_debug, "subparse", 0, ".sub parser");
 
Index: gst/subparse/gstsubparse.h
===================================================================
RCS file: /cvs/gstreamer/gst-plugins-base/gst/subparse/gstsubparse.h,v
retrieving revision 1.5
diff -u -r1.5 gstsubparse.h
--- gst/subparse/gstsubparse.h	24 Mar 2006 17:57:39 -0000	1.5
+++ gst/subparse/gstsubparse.h	26 Apr 2006 16:06:53 -0000
@@ -45,7 +45,8 @@
   GST_SUB_PARSE_FORMAT_UNKNOWN = 0,
   GST_SUB_PARSE_FORMAT_MDVDSUB = 1,
   GST_SUB_PARSE_FORMAT_SUBRIP = 2,
-  GST_SUB_PARSE_FORMAT_MPSUB = 3
+  GST_SUB_PARSE_FORMAT_MPSUB = 3,
+  GST_SUB_PARSE_FORMAT_SAMI = 4
 } GstSubParseFormat;
 
 typedef struct {
@@ -54,6 +55,7 @@
   guint64  start_time;
   guint64  duration;
   GstSegment *segment;
+  gpointer *user_data;
 } ParserState;
 
 typedef gchar* (*Parser) (ParserState *state, const gchar *line);
