subparse: add qttext support
authorThiago Santos <thiago.sousa.santos@collabora.co.uk>
Tue, 1 Dec 2009 16:13:24 +0000 (13:13 -0300)
committerThiago Santos <thiago.sousa.santos@collabora.co.uk>
Tue, 1 Dec 2009 17:06:27 +0000 (14:06 -0300)
Adds basic support for qttext subtitles, still lacks markup tags
to make it prettier, but the plain text already works.

Implemented according to:
http://www.apple.com/quicktime/tutorials/texttracks.html
http://www.apple.com/quicktime/tutorials/textdescriptors.html

Fixes #603357

gst/subparse/Makefile.am
gst/subparse/gstsubparse.c
gst/subparse/gstsubparse.h
gst/subparse/qttextparse.c [new file with mode: 0644]
gst/subparse/qttextparse.h [new file with mode: 0644]

index 1227c26..4a33bd4 100644 (file)
@@ -15,7 +15,9 @@ libgstsubparse_la_SOURCES = \
        tmplayerparse.c \
        tmplayerparse.h \
        mpl2parse.c \
-       mpl2parse.h
+       mpl2parse.h \
+        qttextparse.c \
+        qttextparse.h
 
 libgstsubparse_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS)
 libgstsubparse_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
@@ -27,4 +29,5 @@ noinst_HEADERS = \
        gstsubparse.h \
        samiparse.h \
        tmplayerparse.h \
-       mpl2parse.h
+       mpl2parse.h \
+        qttextparse.h
index 41a1c7e..dc26f65 100644 (file)
@@ -34,6 +34,7 @@
 #include "samiparse.h"
 #include "tmplayerparse.h"
 #include "mpl2parse.h"
+#include "qttextparse.h"
 
 GST_DEBUG_CATEGORY (sub_parse_debug);
 
@@ -67,14 +68,15 @@ static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_ALWAYS,
     GST_STATIC_CAPS ("application/x-subtitle; application/x-subtitle-sami; "
         "application/x-subtitle-tmplayer; application/x-subtitle-mpl2; "
-        "application/x-subtitle-dks")
+        "application/x-subtitle-dks; application/x-subtitle-qttext")
     );
 #else
 static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
     GST_STATIC_CAPS ("application/x-subtitle; application/x-subtitle-dks; "
-        "application/x-subtitle-tmplayer; application/x-subtitle-mpl2")
+        "application/x-subtitle-tmplayer; application/x-subtitle-mpl2; "
+        "application/x-subtitle-qttext")
     );
 #endif
 
@@ -143,6 +145,19 @@ gst_sub_parse_dispose (GObject * object)
 
   GST_DEBUG_OBJECT (subparse, "cleaning up subtitle parser");
 
+  switch (subparse->parser_type) {
+    case GST_SUB_PARSE_FORMAT_QTTEXT:
+      qttext_context_deinit (&subparse->state);
+      break;
+#ifndef GST_DISABLE_XML
+    case GST_SUB_PARSE_FORMAT_SAMI:
+      sami_context_deinit (&subparse->state);
+      break;
+#endif
+    default:
+      break;
+  }
+
   if (subparse->encoding) {
     g_free (subparse->encoding);
     subparse->encoding = NULL;
@@ -162,10 +177,6 @@ gst_sub_parse_dispose (GObject * object)
     g_string_free (subparse->textbuf, TRUE);
     subparse->textbuf = NULL;
   }
-#ifndef GST_DISABLE_XML
-  if (subparse->parser_type == GST_SUB_PARSE_FORMAT_SAMI)
-    sami_context_deinit (&subparse->state);
-#endif
 
   GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
 }
@@ -422,6 +433,8 @@ gst_sub_parse_get_format_description (GstSubParseFormat format)
       return "SubViewer";
     case GST_SUB_PARSE_FORMAT_DKS:
       return "DKS";
+    case GST_SUB_PARSE_FORMAT_QTTEXT:
+      return "QTtext";
     default:
     case GST_SUB_PARSE_FORMAT_UNKNOWN:
       break;
@@ -1196,11 +1209,17 @@ parser_state_dispose (GstSubParse * self, ParserState * state)
     g_string_free (state->buf, TRUE);
     state->buf = NULL;
   }
+  if (state->user_data) {
+    switch (self->parser_type) {
 #ifndef GST_DISABLE_XML
-  if (state->user_data && self->parser_type == GST_SUB_PARSE_FORMAT_SAMI) {
-    sami_context_reset (state);
-  }
+      case GST_SUB_PARSE_FORMAT_SAMI:
+        sami_context_reset (state);
+        break;
 #endif
+      default:
+        break;
+    }
+  }
 }
 
 /* regex type enum */
@@ -1324,6 +1343,10 @@ gst_sub_parse_data_format_autodetect (gchar * match_str)
     GST_LOG ("SubViewer (time based) format detected");
     return GST_SUB_PARSE_FORMAT_SUBVIEWER;
   }
+  if (strstr (match_str, "{QTtext}") != NULL) {
+    GST_LOG ("QTtext (time based) format detected");
+    return GST_SUB_PARSE_FORMAT_QTTEXT;
+  }
 
   GST_DEBUG ("no subtitle format detected");
   return GST_SUB_PARSE_FORMAT_UNKNOWN;
@@ -1377,6 +1400,10 @@ gst_sub_parse_format_autodetect (GstSubParse * self)
     case GST_SUB_PARSE_FORMAT_SUBVIEWER:
       self->parse_line = parse_subviewer;
       return gst_caps_new_simple ("text/plain", NULL);
+    case GST_SUB_PARSE_FORMAT_QTTEXT:
+      self->parse_line = parse_qttext;
+      qttext_context_init (&self->state);
+      return gst_caps_new_simple ("text/x-pango-markup", NULL);
     case GST_SUB_PARSE_FORMAT_UNKNOWN:
     default:
       GST_DEBUG ("no subtitle format detected");
@@ -1706,6 +1733,10 @@ static GstStaticCaps smi_caps = GST_STATIC_CAPS ("application/x-subtitle-sami");
 static GstStaticCaps dks_caps = GST_STATIC_CAPS ("application/x-subtitle-dks");
 #define DKS_CAPS (gst_static_caps_get (&dks_caps))
 
+static GstStaticCaps qttext_caps =
+GST_STATIC_CAPS ("application/x-subtitle-qttext");
+#define QTTEXT_CAPS (gst_static_caps_get (&qttext_caps))
+
 static void
 gst_subparse_type_find (GstTypeFind * tf, gpointer private)
 {
@@ -1811,6 +1842,10 @@ gst_subparse_type_find (GstTypeFind * tf, gpointer private)
       GST_DEBUG ("DKS format detected");
       caps = DKS_CAPS;
       break;
+    case GST_SUB_PARSE_FORMAT_QTTEXT:
+      GST_DEBUG ("QTtext format detected");
+      caps = QTTEXT_CAPS;
+      break;
     default:
     case GST_SUB_PARSE_FORMAT_UNKNOWN:
       GST_DEBUG ("no subtitle format detected");
index 13924d3..5731d91 100644 (file)
@@ -54,7 +54,8 @@ typedef enum
   GST_SUB_PARSE_FORMAT_TMPLAYER = 5,
   GST_SUB_PARSE_FORMAT_MPL2 = 6,
   GST_SUB_PARSE_FORMAT_SUBVIEWER = 7,
-  GST_SUB_PARSE_FORMAT_DKS = 8
+  GST_SUB_PARSE_FORMAT_DKS = 8,
+  GST_SUB_PARSE_FORMAT_QTTEXT = 9
 } GstSubParseFormat;
 
 typedef struct {
diff --git a/gst/subparse/qttextparse.c b/gst/subparse/qttextparse.c
new file mode 100644 (file)
index 0000000..c056940
--- /dev/null
@@ -0,0 +1,195 @@
+/* GStreamer QTtext subtitle parser
+ * Copyright (c) 2009 Thiago Santos <thiago.sousa.santos collabora co uk>>
+ *
+ * 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.
+ */
+
+#include "qttextparse.h"
+
+#include <string.h>
+
+#define MIN_TO_NSEC  (60 * GST_SECOND)
+#define HOUR_TO_NSEC (60 * MIN_TO_NSEC)
+
+#define GST_QTTEXT_CONTEXT(state) ((GstQTTextContext *) (state)->user_data)
+
+typedef struct _GstQTTextContext GstQTTextContext;
+
+struct _GstQTTextContext
+{
+  /* timing variables */
+  gint timescale;
+  gboolean absolute;
+  guint64 start_time;
+
+};
+
+void
+qttext_context_init (ParserState * state)
+{
+  GstQTTextContext *context;
+
+  state->user_data = g_new0 (GstQTTextContext, 1);
+
+  context = GST_QTTEXT_CONTEXT (state);
+
+  /* we use 1000 as a default */
+  context->timescale = 1000;
+  context->absolute = TRUE;
+}
+
+void
+qttext_context_deinit (ParserState * state)
+{
+  g_free (state->user_data);
+  state->user_data = NULL;
+}
+
+static gboolean
+qttext_parse_tag (ParserState * state, const gchar * line, gint * index)
+{
+  gchar *next;
+  gint next_index;
+
+  g_assert (line[*index] == '{');
+
+  next = strchr (line + *index, '}');
+  if (next == NULL) {
+    goto error_out;
+  } else {
+    next_index = 1 + (next - line);
+  }
+  g_assert (line[next_index - 1] == '}');
+
+  *index = *index + 1;          /* skip the { */
+
+  /* now identify our tag */
+  if (strncmp (line + *index, "QTtext", 6) == 0) {
+    /* NOP */
+  } else {
+    GST_WARNING ("Unused qttext tag starting at: %s", line + *index);
+  }
+
+  *index = next_index;
+  return TRUE;
+
+error_out:
+  {
+    GST_WARNING ("Failed to parse qttext tag at line %s", line);
+    return FALSE;
+  }
+}
+
+static guint64
+qttext_parse_timestamp (ParserState * state, const gchar * line, gint index)
+{
+  int ret;
+  gint hour, min, sec, dec;
+  GstQTTextContext *context = GST_QTTEXT_CONTEXT (state);
+
+  ret = sscanf (line + index, "[%d:%d:%d.%d]", &hour, &min, &sec, &dec);
+  if (ret != 3 && ret != 4) {
+    /* bad timestamp */
+    GST_WARNING ("Bad qttext timestamp found: %s", line);
+    return 0;
+  }
+
+  if (ret == 3) {
+    /* be forgiving for missing decimal part */
+    dec = 0;
+  }
+
+  /* parse the decimal part according to the timescale */
+  g_assert (context->timescale != 0);
+  dec = (GST_SECOND * dec) / context->timescale;
+
+  /* return the result */
+  return hour * HOUR_TO_NSEC + min * MIN_TO_NSEC + sec * GST_SECOND + dec;
+}
+
+static void
+qttext_prepare_text (ParserState * state)
+{
+  if (state->buf == NULL) {
+    state->buf = g_string_sized_new (256);      /* this should be enough */
+  } else {
+    g_string_append (state->buf, "\n");
+  }
+
+  /* TODO add the pango markup */
+}
+
+static void
+qttext_parse_text (ParserState * state, const gchar * line, gint index)
+{
+  qttext_prepare_text (state);
+  g_string_append (state->buf, line + index);
+}
+
+gchar *
+parse_qttext (ParserState * state, const gchar * line)
+{
+  gint i;
+  guint64 ts;
+  gchar *ret = NULL;
+  GstQTTextContext *context = GST_QTTEXT_CONTEXT (state);
+
+  i = 0;
+  while (line[i] != '\0') {
+    /* find first interesting character from 'i' onwards */
+
+    if (line[i] == '{') {
+      /* this is a tag, parse it */
+      if (!qttext_parse_tag (state, line, &i)) {
+        break;
+      }
+    } else if (line[i] == '[') {
+      /* this is a time, convert it to a timestamp */
+      ts = qttext_parse_timestamp (state, line, i);
+
+      /* check if we have pending text to send, in case we prepare it */
+      if (state->buf) {
+        ret = g_string_free (state->buf, FALSE);
+        if (context->absolute)
+          state->duration = ts - context->start_time;
+        else
+          state->duration = ts;
+        state->start_time = context->start_time;
+      }
+      state->buf = NULL;
+
+      if (ts == 0) {
+        /* this is an error */
+      } else {
+        if (context->absolute)
+          context->start_time = ts;
+        else
+          context->start_time += ts;
+      }
+
+      /* we assume there is nothing else on this line */
+      break;
+
+    } else if (line[i] == ' ' || line[i] == '\t') {
+      i++;                      /* NOP */
+    } else {
+      /* this is the actual text, output the rest of the line as it */
+      qttext_parse_text (state, line, i);
+      break;
+    }
+  }
+  return ret;
+}
diff --git a/gst/subparse/qttextparse.h b/gst/subparse/qttextparse.h
new file mode 100644 (file)
index 0000000..6d4f393
--- /dev/null
@@ -0,0 +1,36 @@
+/* GStreamer QTtext subtitle parser
+ * Copyright (c) 2009  Thiago Santos <thiago.sousa.santos collabora co uk>
+ *
+ * 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 _QTTEXT_PARSE_H_
+#define _QTTEXT_PARSE_H_
+
+#include "gstsubparse.h"
+
+G_BEGIN_DECLS
+
+gchar * parse_qttext          (ParserState * state, const gchar * line);
+
+void    qttext_context_init   (ParserState * state);
+
+void    qttext_context_deinit (ParserState * state);
+
+G_END_DECLS
+
+#endif /* _QTTEXT_PARSE_H_ */
+