[MPlayer-dev-eng] [PATCH] Tags support for SubRip and MicroDVD subtitles
ubitux
ubitux at gmail.com
Mon May 17 00:26:53 CEST 2010
Hi,
Well, I've spend my week end on supporting tags for SubRip and MicroDVD
subtitles, and it seems to work fine.
So as expected, I used the libass for rendering. So the patch add a new
file specially focused on the subtitles convertion.
Here is what is supported:
*SubRip*
- Basic tags: italic, bold, underline, strike-through
- Nested font tags with size, color and face attributes
- When tag is not recognized, it is not stripped but display, so a
subtitle with <Hello> inside will be displayed.
You can test it with this kind of subtitle:
This <font color="#112233" size="6" face="Arial">could be <b>the</b> <font size="35" face="Times New Roman">m<font color="#000000">o</font>st</font> difficult thing to</font>implement.
It will gives:
This {\c&H332211&}{\fs6}{\fnArial}could be {\b1}the{\b0} {\fs35}{\fnTimes New Roman}m{\c&H000000&}o{\c&H332211&}st{\fs6}{\fnArial}
*MicroDVD subtitles*
- Style, color, font name, font size, position, coordinates
- Persistence
You can test it something like that:
{667}{705}{Y:i}{o:123,231}Qu’y a-t-il|dans un nom ?
{711}{758}{y:s}{C:00ff00}{f:Times New Roman}Ce que nous|appelons une|{c:ff00c0}rose,
{763}{826}{Y:i}{s:12}embaumerait autant|sous un|autre nom.
{831}{869}{y:ib}Roméo|{Y:u}et|{y:i,z}Juliette
Well, anyway, I attach the full patch so you can test it. Don't forget to
use -ass if you want to get the good rendering. Also, I give the patch in
two versions: SVN one and Git one (thanks to Uoti Urpala for the
ass_enabled trick) in order to get more testing chances :)
Please test it and send me feedback,
Best regards,
--
ubitux
-------------- next part --------------
Index: Makefile
===================================================================
--- Makefile (revision 31179)
+++ Makefile (working copy)
@@ -344,6 +344,7 @@
playtreeparser.c \
spudec.c \
sub_cc.c \
+ subassconvert.c \
subopt-helper.c \
subreader.c \
vobsub.c \
Index: subassconvert.c
===================================================================
--- subassconvert.c (revision 0)
+++ subassconvert.c (revision 0)
@@ -0,0 +1,552 @@
+/*
+ * Subtitles converter to SSA/ASS in order to allow special formatting
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "subreader.h"
+#include "subassconvert.h"
+
+#define SUB_MAX_FONTSTRING_LEN 32
+
+static int insert_text_in_subdest(char *dst, int *j, char *src, int srclen)
+{
+ if (!(*j + srclen + 1 < LINE_LEN))
+ return -1;
+ strcpy(&dst[*j], src);
+ *j += srclen;
+ return 0;
+}
+
+
+/*
+ * SubRip
+ *
+ * Support basic tags (italic, bold, underline, strike-through)
+ * and font tag with size, color and face attributes.
+ *
+ */
+
+typedef struct {
+ int field;
+ long int size;
+ uint32_t color;
+ char face[SUB_MAX_FONTSTRING_LEN];
+} font_stack_tag;
+
+#define SUBRIP_FIELD_FONT_SIZE (1 << 0)
+#define SUBRIP_FIELD_FONT_COLOR (1 << 1)
+#define SUBRIP_FIELD_FONT_FACE (1 << 2)
+
+static const struct {
+ char *from;
+ char *to;
+} subrip_basic_tags[] = {
+ {"<i>", "{\\i1}"}, {"</i>", "{\\i0}"},
+ {"<b>", "{\\b1}"}, {"</b>", "{\\b0}"},
+ {"<u>", "{\\u1}"}, {"</u>", "{\\u0}"},
+ {"<s>", "{\\s1}"}, {"</s>", "{\\s0}"}
+};
+
+#define SUBRIP_MAX_STACKED_FONT_TAGS 16
+
+static void *subrip_get_last_tag_setting(font_stack_tag *fstack, int sp, int flag)
+{
+ while (sp > 0) {
+ font_stack_tag *tag;
+
+ sp--;
+ tag = &fstack[sp];
+ switch (tag->field & flag) {
+ case SUBRIP_FIELD_FONT_SIZE:
+ return &tag->size;
+ case SUBRIP_FIELD_FONT_COLOR:
+ return &tag->color;
+ case SUBRIP_FIELD_FONT_FACE:
+ return tag->face;
+ default:
+ break;
+ }
+ }
+ return NULL;
+}
+
+void subassconvert_subrip(char *line)
+{
+ char new_line[LINE_LEN + 1];
+ char *start_line = line;
+ int j = 0;
+ font_stack_tag font_stack[SUBRIP_MAX_STACKED_FONT_TAGS];
+ int sp = 0;
+
+ while (*line && j < sizeof(new_line) - 1) {
+ int n, match = 0;
+
+ /* Basic tags */
+ for (n = 0; n < (int)(sizeof(subrip_basic_tags) / sizeof(*subrip_basic_tags)); n++) {
+ int from_len = strlen(subrip_basic_tags[n].from);
+ int to_len = strlen(subrip_basic_tags[n].to);
+
+ if (strncmp(line, subrip_basic_tags[n].from, from_len) == 0) {
+ insert_text_in_subdest(new_line, &j, subrip_basic_tags[n].to, to_len);
+ line += from_len;
+ match = 1;
+ break;
+ }
+ }
+
+ if (!match) {
+
+ /* Closing font tag */
+ if (strncmp(line, "</font>", 7) == 0) {
+ line += 7;
+ match = 1;
+
+ if (sp > 0) {
+ font_stack_tag *tag;
+
+ sp--;
+ tag = &font_stack[sp];
+
+ if (tag->field & SUBRIP_FIELD_FONT_SIZE) {
+ void *last_setting_ptr = subrip_get_last_tag_setting(font_stack, sp, SUBRIP_FIELD_FONT_SIZE);
+
+ if (last_setting_ptr == NULL) {
+ insert_text_in_subdest(new_line, &j, "{\\fs}", sizeof("{\\fs}") - 1);
+ } else {
+ char stag[4 + sizeof(tag->size) + 2];
+
+ sprintf(stag, "{\\fs%ld}", *(long int *)last_setting_ptr);
+ insert_text_in_subdest(new_line, &j, stag, strlen(stag));
+ }
+ }
+
+ if (tag->field & SUBRIP_FIELD_FONT_COLOR) {
+ void *last_setting_ptr = subrip_get_last_tag_setting(font_stack, sp, SUBRIP_FIELD_FONT_COLOR);
+
+ if (last_setting_ptr == NULL) {
+ insert_text_in_subdest(new_line, &j, "{\\c}", sizeof("{\\c}") - 1);
+ } else {
+ char ctag[] = "{\\c&Hbbggrr&}";
+
+ sprintf(ctag, "{\\c&H%06X&}", *(uint32_t *)last_setting_ptr);
+ insert_text_in_subdest(new_line, &j, ctag, sizeof(ctag) - 1);
+ }
+ }
+
+ if (tag->field & SUBRIP_FIELD_FONT_FACE) {
+ void *last_setting_ptr = subrip_get_last_tag_setting(font_stack, sp, SUBRIP_FIELD_FONT_FACE);
+
+ if (last_setting_ptr == NULL) {
+ insert_text_in_subdest(new_line, &j, "{\\fn}", sizeof("{\\fn}") - 1);
+ } else {
+ char ftag[4 + sizeof(tag->face) + 2];
+ int len = sprintf(ftag, "{\\fn%s}", (char *)last_setting_ptr);
+
+ insert_text_in_subdest(new_line, &j, ftag, len);
+ }
+ }
+ }
+
+ /* Opening font tag */
+ } else if (strncmp(line, "<font ", 6) == 0) {
+ char *potential_font_tag_start = line;
+ int j_backup = j;
+ font_stack_tag *tag = &font_stack[sp];
+
+ if (sp < sizeof(font_stack)) {
+ tag->field = 0;
+ line += 6;
+
+ while (*line && *line != '>') {
+
+ /* Size attribute */
+ if (strncmp(line, "size=\"", 6) == 0) {
+ char stag[4 + sizeof(tag->size) + 2];
+ int len;
+
+ line += 6;
+ tag->size = strtol(line, &line, 10);
+ if (*line != '"')
+ break;
+ len = sprintf(stag, "{\\fs%ld}", tag->size);
+ insert_text_in_subdest(new_line, &j, stag, len);
+ tag->field |= SUBRIP_FIELD_FONT_SIZE;
+
+ /* Color attribute */
+ } else if (strncmp(line, "color=\"#", 8) == 0) {
+ char ctag[] = "{\\c&Hbbggrr&}";
+
+ line += 8;
+ tag->color = (uint32_t)strtol(line, &line, 16) & 0x00ffffff;
+ tag->color = ((tag->color & 0xff) << 16) | (tag->color & 0xff00) | ((tag->color & 0xff0000) >> 16);
+ if (*line != '"')
+ break;
+ sprintf(ctag, "{\\c&H%06X&}", tag->color);
+ insert_text_in_subdest(new_line, &j, ctag, sizeof(ctag) - 1);
+ tag->field |= SUBRIP_FIELD_FONT_COLOR;
+
+ /* Font face attribute */
+ } else if (strncmp(line, "face=\"", 6) == 0) {
+ int i = 0, len;
+ char ftag[4 + sizeof(tag->face) + 2];
+
+ line += 6;
+ while (*line && *line != '"' && i < sizeof(tag->face) - 1)
+ tag->face[i++] = *line++;
+ tag->face[i] = 0;
+ if (*line != '"')
+ break;
+ len = sprintf(ftag, "{\\fn%s}", tag->face);
+ insert_text_in_subdest(new_line, &j, ftag, len);
+ tag->field |= SUBRIP_FIELD_FONT_FACE;
+ }
+
+ line++;
+ }
+
+ if (!tag->field || *line != '>') { /* Not valid font tag */
+ line = potential_font_tag_start;
+ j = j_backup;
+ } else {
+ match = 1;
+ sp++;
+ line++;
+ }
+ }
+ }
+ }
+
+ /* Normal text */
+ if (!match)
+ new_line[j++] = *line++;
+ }
+ new_line[j] = 0;
+ strcpy(start_line, new_line);
+}
+
+
+/*
+ * MicroDVD
+ *
+ * Based on the specifications found here:
+ * https://trac.videolan.org/vlc/ticket/1825#comment:6
+ */
+
+static int indexof(const char *s, int c)
+{
+ char *f = strchr(s, c);
+
+ return (f) ? (f - s) : -1;
+}
+
+typedef struct {
+ char key;
+ int persistent;
+ uint32_t data1;
+ uint32_t data2;
+ char data_string[SUB_MAX_FONTSTRING_LEN];
+} microdvd_tag_type;
+
+#define MICRODVD_DEAD_TAG {MICRODVD_TAG_DISABLED, MICRODVD_PERSISTENT_OFF, 0, 0, ""}
+
+#define MICRODVD_PERSISTENT_OFF 0
+#define MICRODVD_PERSISTENT_ON 1
+#define MICRODVD_PERSISTENT_OPENED 2
+
+#define MICRODVD_TAGS "cfshyYpo" /* Color, Font, Size, cHarset, stYle, Position, cOordinate */
+
+static void microdvd_set_tag(microdvd_tag_type *tags, microdvd_tag_type tag)
+{
+ int tag_index = indexof(MICRODVD_TAGS, tag.key);
+
+ if (tag_index < 0)
+ return;
+ memcpy(&tags[tag_index], &tag, sizeof(tag));
+}
+
+#define MICRODVD_STYLES "ibus" /* italic, bold, underline, strike-through */
+#define MICRODVD_TAG_DISABLED '.'
+
+static char *microdvd_load_tags(microdvd_tag_type *tags, char *s)
+{
+ while (*s == '{') {
+ char *start = s;
+ char tag_char = *(s + 1);
+ microdvd_tag_type tag = MICRODVD_DEAD_TAG;
+
+ if (!tag_char || *(s + 2) != ':')
+ break;
+ s += 3;
+
+ switch (tag_char) {
+
+ /* Style */
+ case 'Y':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ case 'y':
+ while (*s && *s != '}') {
+ int style_index = indexof(MICRODVD_STYLES, *s);
+
+ if (style_index >= 0)
+ tag.data1 |= (1 << style_index);
+ s++;
+ }
+ if (*s != '}')
+ break;
+ tag.key = tag_char; /* We must distinct persistent and non-persistent styles
+ to handle that kind of style tags: {y:ib}{Y:us} */
+ break;
+
+ /* Color */
+ case 'C':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ case 'c':
+ tag.data1 = (uint32_t)strtol(s, &s, 16) & 0x00ffffff;
+ if (*s != '}')
+ break;
+ tag.key = 'c';
+ break;
+
+ /* Font name */
+ case 'F':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ case 'f':
+ {
+ int i = 0;
+
+ while (*s && *s != '}' && i < sizeof(tag.data_string))
+ tag.data_string[i++] = *s++;
+ if (*s != '}')
+ break;
+ tag.key = 'f';
+ break;
+ }
+
+ /* Font size */
+ case 'S':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ case 's':
+ {
+ tag.data1 = (uint32_t)strtol(s, &s, 10);
+ if (*s != '}' || tag.data1 > 72)
+ break;
+ tag.key = 's';
+ break;
+ }
+
+ /* Charset */
+ case 'H':
+ {
+ int i = 0;
+
+ //TODO: not yet handled, just parsed.
+ while (*s && *s != '}' && i < sizeof(tag.data_string))
+ tag.data_string[i++] = *s++;
+ if (*s != '}')
+ break;
+ tag.key = 'h';
+ break;
+ }
+
+ /* Position */
+ case 'P':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ tag.data1 = (*s++ == '1');
+ if (*s != '}')
+ break;
+ tag.key = 'p';
+ break;
+
+ /* Coordinates */
+ case 'o':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ tag.data1 = (uint32_t)strtol(s, &s, 10);
+ if (*s != ',')
+ break;
+ s++;
+ tag.data2 = (uint32_t)strtol(s, &s, 10);
+ if (*s != '}')
+ break;
+ tag.key = 'o';
+ break;
+
+ default: /* Unknown tag, we considere it's text */
+ break;
+ }
+
+ if (tag.key == MICRODVD_TAG_DISABLED)
+ return start;
+
+ microdvd_set_tag(tags, tag);
+ s++;
+ }
+ return s;
+}
+
+static void microdvd_open_tags(char *new_line, int *j, microdvd_tag_type *tags, int persistence)
+{
+ int i;
+
+ for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
+ if (tags[i].persistent != persistence)
+ continue;
+ switch (tags[i].key) {
+ case 'Y':
+ case 'y':
+ {
+ int style_index = 0;
+ char ytag[] = "{\\.1}";
+
+ while (style_index < sizeof(MICRODVD_STYLES) - 1) {
+ int style_mask = (1 << style_index);
+
+ if (tags[i].data1 & style_mask) {
+ ytag[2] = MICRODVD_STYLES[style_index];
+ insert_text_in_subdest(new_line, j, ytag, sizeof(ytag) - 1);
+ }
+ style_index++;
+ }
+ break;
+ }
+
+ case 'c':
+ {
+ char ctag[] = "{\\c&Hbbggrr&}";
+
+ sprintf(ctag, "{\\c&H%06X&}", tags[i].data1);
+ insert_text_in_subdest(new_line, j, ctag, sizeof(ctag) - 1);
+ break;
+ }
+
+ case 'f':
+ {
+ char ftag[4 + sizeof(tags[0].data_string) + 2];
+ int len = sprintf(ftag, "{\\fn%s}", tags[i].data_string);
+
+ insert_text_in_subdest(new_line, j, ftag, len);
+ break;
+ }
+
+ case 's':
+ {
+ char stag[4 + 2 + 2];
+ int len = sprintf(stag, "{\\fs%d}", tags[i].data1);
+
+ insert_text_in_subdest(new_line, j, stag, len);
+ break;
+ }
+
+ case 'p':
+ if (tags[i].data1 == 0)
+ insert_text_in_subdest(new_line, j, "{\\an8}", sizeof("{\\an8}") - 1);
+ break;
+
+ case 'o':
+ {
+ char otag[5 + sizeof(tags[0].data1) * 2 + 3];
+ int len = sprintf(otag, "{\\pos(%d,%d)}", tags[i].data1, tags[i].data2);
+
+ insert_text_in_subdest(new_line, j, otag, len);
+ break;
+ }
+
+ default:
+ break;
+ }
+ if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
+ tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
+ }
+}
+
+static void microdvd_close_no_persistent_tags(char *new_line, int *j, microdvd_tag_type *tags)
+{
+ int i;
+
+ for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) {
+ if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
+ continue;
+ switch (tags[i].key) {
+ case 'y':
+ {
+ int style_index = sizeof(MICRODVD_STYLES) - 2;
+ char ctag[] = "{\\.0}";
+
+ while (style_index >= 0) {
+ int style_mask = (1 << style_index);
+
+ if (tags[i].data1 & style_mask) {
+ ctag[2] = MICRODVD_STYLES[style_index];
+ insert_text_in_subdest(new_line, j, ctag, sizeof(ctag) - 1);
+ }
+ style_index--;
+ }
+ break;
+ }
+
+ case 'c':
+ insert_text_in_subdest(new_line, j, "{\\c}", sizeof("{\\c}") - 1);
+ break;
+
+ case 'f':
+ insert_text_in_subdest(new_line, j, "{\\fn}", sizeof("{\\fn}") - 1);
+ break;
+
+ case 's':
+ insert_text_in_subdest(new_line, j, "{\\fs}", sizeof("{\\fs}") - 1);
+ break;
+
+ default:
+ break;
+ }
+ tags[i].key = MICRODVD_TAG_DISABLED;
+ }
+}
+
+void subassconvert_microdvd(char *line)
+{
+ int i, j = 0;
+ char new_line[LINE_LEN + 1];
+ char *line_start = line;
+ microdvd_tag_type tags[sizeof(MICRODVD_TAGS) - 1];
+ const microdvd_tag_type dead_tag = MICRODVD_DEAD_TAG;
+
+ for (i = 0; MICRODVD_TAGS[i]; i++)
+ memcpy(&tags[i], &dead_tag, sizeof(dead_tag));
+
+ while (*line) {
+ line = microdvd_load_tags(tags, line);
+ microdvd_open_tags(new_line, &j, tags, MICRODVD_PERSISTENT_ON);
+ microdvd_open_tags(new_line, &j, tags, MICRODVD_PERSISTENT_OFF);
+
+ while (*line && *line != '|')
+ new_line[j++] = *line++;
+
+ if (*line == '|') {
+ microdvd_close_no_persistent_tags(new_line, &j, tags);
+ insert_text_in_subdest(new_line, &j, "\\N", sizeof("\\N") - 1);
+ line++;
+ }
+ }
+
+ new_line[j] = 0;
+ strcpy(line_start, new_line);
+}
Index: subassconvert.h
===================================================================
--- subassconvert.h (revision 0)
+++ subassconvert.h (revision 0)
@@ -0,0 +1,27 @@
+/*
+ * Header for subtitles converter to SSA/ASS
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_SUBASSCONVERT_H
+#define MPLAYER_SUBASSCONVERT_H
+
+void subassconvert_subrip(char *line);
+void subassconvert_microdvd(char *line);
+
+#endif
Index: subreader.c
===================================================================
--- subreader.c (revision 31179)
+++ subreader.c (working copy)
@@ -36,6 +36,9 @@
#include "libavutil/common.h"
#include "libavutil/avstring.h"
+#include "libass/ass_mp.h"
+#include "subassconvert.h"
+
#ifdef CONFIG_ENCA
#include <enca.h>
#endif
@@ -55,8 +58,6 @@
extern char* dvdsub_lang;
-/* Maximal length of line of a subtitle */
-#define LINE_LEN 1000
static float mpsub_position=0;
static float mpsub_multiplier=1.;
static int sub_slacktime = 20000; //20 sec
@@ -291,6 +292,9 @@
p=line2;
+ if (ass_enabled)
+ subassconvert_microdvd(p);
+
next=p, i=0;
while ((next =sub_readtext (next, &(current->text[i])))) {
if (current->text[i]==ERR) {return ERR;}
@@ -373,6 +377,10 @@
for (i=0; i<SUB_MAX_TEXT;) {
int blank = 1;
if (!stream_read_line (st, line, LINE_LEN, utf16)) break;
+
+ if (ass_enabled)
+ subassconvert_subrip(line);
+
len=0;
for (p=line; *p!='\n' && *p!='\r' && *p; p++,len++)
if (*p != ' ' && *p != '\t')
@@ -384,17 +392,19 @@
//strncpy (current->text[i], line, len); current->text[i][len]='\0';
for(; j<len; j++) {
/* let's filter html tags ::atmos */
- if(line[j]=='>') {
- skip=0;
- continue;
+ if (!ass_enabled) {
+ if(line[j]=='>') {
+ skip=0;
+ continue;
+ }
+ if(line[j]=='<') {
+ skip=1;
+ continue;
+ }
+ if(skip) {
+ continue;
+ }
}
- if(line[j]=='<') {
- skip=1;
- continue;
- }
- if(skip) {
- continue;
- }
*curptr=line[j];
curptr++;
}
Index: subreader.h
===================================================================
--- subreader.h (revision 31179)
+++ subreader.h (working copy)
@@ -44,6 +44,9 @@
#define SUB_JACOSUB 12
#define SUB_MPL2 13
+/* Maximal length of line of a subtitle */
+#define LINE_LEN 1000
+
// One of the SUB_* constant above
extern int sub_format;
-------------- next part --------------
diff --git a/Makefile b/Makefile
index d9cb34d..68f399e 100644
--- a/Makefile
+++ b/Makefile
@@ -348,6 +348,7 @@ SRCS_COMMON = asxparser.c \
playtreeparser.c \
spudec.c \
sub_cc.c \
+ subassconvert.c \
subopt-helper.c \
subreader.c \
talloc.c \
diff --git a/mencoder.c b/mencoder.c
index dc3d870..f7d08b4 100644
--- a/mencoder.c
+++ b/mencoder.c
@@ -346,7 +346,7 @@ static void add_subtitles(char *filename, float fps, int silent)
if (!filename) return;
- subd = sub_read_file(filename, fps);
+ subd = sub_read_file(filename, fps, &opts);
#ifdef CONFIG_ASS
if (opts.ass_enabled)
#ifdef CONFIG_ICONV
diff --git a/mplayer.c b/mplayer.c
index beb1af5..356f6d1 100644
--- a/mplayer.c
+++ b/mplayer.c
@@ -1119,7 +1119,7 @@ void add_subtitles(struct MPContext *mpctx, char *filename, float fps, int noerr
asst = ass_read_stream(ass_library, filename, 0);
#endif
if (!asst) {
- subd = sub_read_file(filename, fps);
+ subd = sub_read_file(filename, fps, &mpctx->opts);
if (subd) {
asst = ass_read_subdata(ass_library, subd, fps);
if (asst) {
@@ -1130,7 +1130,7 @@ void add_subtitles(struct MPContext *mpctx, char *filename, float fps, int noerr
}
} else
#endif
- subd = sub_read_file(filename, fps);
+ subd = sub_read_file(filename, fps, &mpctx->opts);
if (!asst && !subd) {
diff --git a/subassconvert.c b/subassconvert.c
new file mode 100644
index 0000000..41df121
--- /dev/null
+++ b/subassconvert.c
@@ -0,0 +1,552 @@
+/*
+ * Subtitles converter to SSA/ASS in order to allow special formatting
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "subreader.h"
+#include "subassconvert.h"
+
+#define SUB_MAX_FONTSTRING_LEN 32
+
+static int insert_text_in_subdest(char *dst, int *j, char *src, int srclen)
+{
+ if (!(*j + srclen + 1 < LINE_LEN))
+ return -1;
+ strcpy(&dst[*j], src);
+ *j += srclen;
+ return 0;
+}
+
+
+/*
+ * SubRip
+ *
+ * Support basic tags (italic, bold, underline, strike-through)
+ * and font tag with size, color and face attributes.
+ *
+ */
+
+typedef struct {
+ int field;
+ long int size;
+ uint32_t color;
+ char face[SUB_MAX_FONTSTRING_LEN];
+} font_stack_tag;
+
+#define SUBRIP_FIELD_FONT_SIZE (1 << 0)
+#define SUBRIP_FIELD_FONT_COLOR (1 << 1)
+#define SUBRIP_FIELD_FONT_FACE (1 << 2)
+
+static const struct {
+ char *from;
+ char *to;
+} subrip_basic_tags[] = {
+ {"<i>", "{\\i1}"}, {"</i>", "{\\i0}"},
+ {"<b>", "{\\b1}"}, {"</b>", "{\\b0}"},
+ {"<u>", "{\\u1}"}, {"</u>", "{\\u0}"},
+ {"<s>", "{\\s1}"}, {"</s>", "{\\s0}"}
+};
+
+#define SUBRIP_MAX_STACKED_FONT_TAGS 16
+
+static void *subrip_get_last_tag_setting(font_stack_tag *fstack, int sp, int flag)
+{
+ while (sp > 0) {
+ font_stack_tag *tag;
+
+ sp--;
+ tag = &fstack[sp];
+ switch (tag->field & flag) {
+ case SUBRIP_FIELD_FONT_SIZE:
+ return &tag->size;
+ case SUBRIP_FIELD_FONT_COLOR:
+ return &tag->color;
+ case SUBRIP_FIELD_FONT_FACE:
+ return tag->face;
+ default:
+ break;
+ }
+ }
+ return NULL;
+}
+
+void subassconvert_subrip(char *line)
+{
+ char new_line[LINE_LEN + 1];
+ char *start_line = line;
+ int j = 0;
+ font_stack_tag font_stack[SUBRIP_MAX_STACKED_FONT_TAGS];
+ int sp = 0;
+
+ while (*line && j < sizeof(new_line) - 1) {
+ int n, match = 0;
+
+ /* Basic tags */
+ for (n = 0; n < (int)(sizeof(subrip_basic_tags) / sizeof(*subrip_basic_tags)); n++) {
+ int from_len = strlen(subrip_basic_tags[n].from);
+ int to_len = strlen(subrip_basic_tags[n].to);
+
+ if (strncmp(line, subrip_basic_tags[n].from, from_len) == 0) {
+ insert_text_in_subdest(new_line, &j, subrip_basic_tags[n].to, to_len);
+ line += from_len;
+ match = 1;
+ break;
+ }
+ }
+
+ if (!match) {
+
+ /* Closing font tag */
+ if (strncmp(line, "</font>", 7) == 0) {
+ line += 7;
+ match = 1;
+
+ if (sp > 0) {
+ font_stack_tag *tag;
+
+ sp--;
+ tag = &font_stack[sp];
+
+ if (tag->field & SUBRIP_FIELD_FONT_SIZE) {
+ void *last_setting_ptr = subrip_get_last_tag_setting(font_stack, sp, SUBRIP_FIELD_FONT_SIZE);
+
+ if (last_setting_ptr == NULL) {
+ insert_text_in_subdest(new_line, &j, "{\\fs}", sizeof("{\\fs}") - 1);
+ } else {
+ char stag[4 + sizeof(tag->size) + 2];
+
+ sprintf(stag, "{\\fs%ld}", *(long int *)last_setting_ptr);
+ insert_text_in_subdest(new_line, &j, stag, strlen(stag));
+ }
+ }
+
+ if (tag->field & SUBRIP_FIELD_FONT_COLOR) {
+ void *last_setting_ptr = subrip_get_last_tag_setting(font_stack, sp, SUBRIP_FIELD_FONT_COLOR);
+
+ if (last_setting_ptr == NULL) {
+ insert_text_in_subdest(new_line, &j, "{\\c}", sizeof("{\\c}") - 1);
+ } else {
+ char ctag[] = "{\\c&Hbbggrr&}";
+
+ sprintf(ctag, "{\\c&H%06X&}", *(uint32_t *)last_setting_ptr);
+ insert_text_in_subdest(new_line, &j, ctag, sizeof(ctag) - 1);
+ }
+ }
+
+ if (tag->field & SUBRIP_FIELD_FONT_FACE) {
+ void *last_setting_ptr = subrip_get_last_tag_setting(font_stack, sp, SUBRIP_FIELD_FONT_FACE);
+
+ if (last_setting_ptr == NULL) {
+ insert_text_in_subdest(new_line, &j, "{\\fn}", sizeof("{\\fn}") - 1);
+ } else {
+ char ftag[4 + sizeof(tag->face) + 2];
+ int len = sprintf(ftag, "{\\fn%s}", (char *)last_setting_ptr);
+
+ insert_text_in_subdest(new_line, &j, ftag, len);
+ }
+ }
+ }
+
+ /* Opening font tag */
+ } else if (strncmp(line, "<font ", 6) == 0) {
+ char *potential_font_tag_start = line;
+ int j_backup = j;
+ font_stack_tag *tag = &font_stack[sp];
+
+ if (sp < sizeof(font_stack)) {
+ tag->field = 0;
+ line += 6;
+
+ while (*line && *line != '>') {
+
+ /* Size attribute */
+ if (strncmp(line, "size=\"", 6) == 0) {
+ char stag[4 + sizeof(tag->size) + 2];
+ int len;
+
+ line += 6;
+ tag->size = strtol(line, &line, 10);
+ if (*line != '"')
+ break;
+ len = sprintf(stag, "{\\fs%ld}", tag->size);
+ insert_text_in_subdest(new_line, &j, stag, len);
+ tag->field |= SUBRIP_FIELD_FONT_SIZE;
+
+ /* Color attribute */
+ } else if (strncmp(line, "color=\"#", 8) == 0) {
+ char ctag[] = "{\\c&Hbbggrr&}";
+
+ line += 8;
+ tag->color = (uint32_t)strtol(line, &line, 16) & 0x00ffffff;
+ tag->color = ((tag->color & 0xff) << 16) | (tag->color & 0xff00) | ((tag->color & 0xff0000) >> 16);
+ if (*line != '"')
+ break;
+ sprintf(ctag, "{\\c&H%06X&}", tag->color);
+ insert_text_in_subdest(new_line, &j, ctag, sizeof(ctag) - 1);
+ tag->field |= SUBRIP_FIELD_FONT_COLOR;
+
+ /* Font face attribute */
+ } else if (strncmp(line, "face=\"", 6) == 0) {
+ int i = 0, len;
+ char ftag[4 + sizeof(tag->face) + 2];
+
+ line += 6;
+ while (*line && *line != '"' && i < sizeof(tag->face) - 1)
+ tag->face[i++] = *line++;
+ tag->face[i] = 0;
+ if (*line != '"')
+ break;
+ len = sprintf(ftag, "{\\fn%s}", tag->face);
+ insert_text_in_subdest(new_line, &j, ftag, len);
+ tag->field |= SUBRIP_FIELD_FONT_FACE;
+ }
+
+ line++;
+ }
+
+ if (!tag->field || *line != '>') { /* Not valid font tag */
+ line = potential_font_tag_start;
+ j = j_backup;
+ } else {
+ match = 1;
+ sp++;
+ line++;
+ }
+ }
+ }
+ }
+
+ /* Normal text */
+ if (!match)
+ new_line[j++] = *line++;
+ }
+ new_line[j] = 0;
+ strcpy(start_line, new_line);
+}
+
+
+/*
+ * MicroDVD
+ *
+ * Based on the specifications found here:
+ * https://trac.videolan.org/vlc/ticket/1825#comment:6
+ */
+
+static int indexof(const char *s, int c)
+{
+ char *f = strchr(s, c);
+
+ return (f) ? (f - s) : -1;
+}
+
+typedef struct {
+ char key;
+ int persistent;
+ uint32_t data1;
+ uint32_t data2;
+ char data_string[SUB_MAX_FONTSTRING_LEN];
+} microdvd_tag_type;
+
+#define MICRODVD_DEAD_TAG {MICRODVD_TAG_DISABLED, MICRODVD_PERSISTENT_OFF, 0, 0, ""}
+
+#define MICRODVD_PERSISTENT_OFF 0
+#define MICRODVD_PERSISTENT_ON 1
+#define MICRODVD_PERSISTENT_OPENED 2
+
+#define MICRODVD_TAGS "cfshyYpo" /* Color, Font, Size, cHarset, stYle, Position, cOordinate */
+
+static void microdvd_set_tag(microdvd_tag_type *tags, microdvd_tag_type tag)
+{
+ int tag_index = indexof(MICRODVD_TAGS, tag.key);
+
+ if (tag_index < 0)
+ return;
+ memcpy(&tags[tag_index], &tag, sizeof(tag));
+}
+
+#define MICRODVD_STYLES "ibus" /* italic, bold, underline, strike-through */
+#define MICRODVD_TAG_DISABLED '.'
+
+static char *microdvd_load_tags(microdvd_tag_type *tags, char *s)
+{
+ while (*s == '{') {
+ char *start = s;
+ char tag_char = *(s + 1);
+ microdvd_tag_type tag = MICRODVD_DEAD_TAG;
+
+ if (!tag_char || *(s + 2) != ':')
+ break;
+ s += 3;
+
+ switch (tag_char) {
+
+ /* Style */
+ case 'Y':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ case 'y':
+ while (*s && *s != '}') {
+ int style_index = indexof(MICRODVD_STYLES, *s);
+
+ if (style_index >= 0)
+ tag.data1 |= (1 << style_index);
+ s++;
+ }
+ if (*s != '}')
+ break;
+ tag.key = tag_char; /* We must distinct persistent and non-persistent styles
+ to handle that kind of style tags: {y:ib}{Y:us} */
+ break;
+
+ /* Color */
+ case 'C':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ case 'c':
+ tag.data1 = (uint32_t)strtol(s, &s, 16) & 0x00ffffff;
+ if (*s != '}')
+ break;
+ tag.key = 'c';
+ break;
+
+ /* Font name */
+ case 'F':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ case 'f':
+ {
+ int i = 0;
+
+ while (*s && *s != '}' && i < sizeof(tag.data_string))
+ tag.data_string[i++] = *s++;
+ if (*s != '}')
+ break;
+ tag.key = 'f';
+ break;
+ }
+
+ /* Font size */
+ case 'S':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ case 's':
+ {
+ tag.data1 = (uint32_t)strtol(s, &s, 10);
+ if (*s != '}' || tag.data1 > 72)
+ break;
+ tag.key = 's';
+ break;
+ }
+
+ /* Charset */
+ case 'H':
+ {
+ int i = 0;
+
+ //TODO: not yet handled, just parsed.
+ while (*s && *s != '}' && i < sizeof(tag.data_string))
+ tag.data_string[i++] = *s++;
+ if (*s != '}')
+ break;
+ tag.key = 'h';
+ break;
+ }
+
+ /* Position */
+ case 'P':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ tag.data1 = (*s++ == '1');
+ if (*s != '}')
+ break;
+ tag.key = 'p';
+ break;
+
+ /* Coordinates */
+ case 'o':
+ tag.persistent = MICRODVD_PERSISTENT_ON;
+ tag.data1 = (uint32_t)strtol(s, &s, 10);
+ if (*s != ',')
+ break;
+ s++;
+ tag.data2 = (uint32_t)strtol(s, &s, 10);
+ if (*s != '}')
+ break;
+ tag.key = 'o';
+ break;
+
+ default: /* Unknown tag, we considere it's text */
+ break;
+ }
+
+ if (tag.key == MICRODVD_TAG_DISABLED)
+ return start;
+
+ microdvd_set_tag(tags, tag);
+ s++;
+ }
+ return s;
+}
+
+static void microdvd_open_tags(char *new_line, int *j, microdvd_tag_type *tags, int persistence)
+{
+ int i;
+
+ for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
+ if (tags[i].persistent != persistence)
+ continue;
+ switch (tags[i].key) {
+ case 'Y':
+ case 'y':
+ {
+ int style_index = 0;
+ char ytag[] = "{\\.1}";
+
+ while (style_index < sizeof(MICRODVD_STYLES) - 1) {
+ int style_mask = (1 << style_index);
+
+ if (tags[i].data1 & style_mask) {
+ ytag[2] = MICRODVD_STYLES[style_index];
+ insert_text_in_subdest(new_line, j, ytag, sizeof(ytag) - 1);
+ }
+ style_index++;
+ }
+ break;
+ }
+
+ case 'c':
+ {
+ char ctag[] = "{\\c&Hbbggrr&}";
+
+ sprintf(ctag, "{\\c&H%06X&}", tags[i].data1);
+ insert_text_in_subdest(new_line, j, ctag, sizeof(ctag) - 1);
+ break;
+ }
+
+ case 'f':
+ {
+ char ftag[4 + sizeof(tags[0].data_string) + 2];
+ int len = sprintf(ftag, "{\\fn%s}", tags[i].data_string);
+
+ insert_text_in_subdest(new_line, j, ftag, len);
+ break;
+ }
+
+ case 's':
+ {
+ char stag[4 + 2 + 2];
+ int len = sprintf(stag, "{\\fs%d}", tags[i].data1);
+
+ insert_text_in_subdest(new_line, j, stag, len);
+ break;
+ }
+
+ case 'p':
+ if (tags[i].data1 == 0)
+ insert_text_in_subdest(new_line, j, "{\\an8}", sizeof("{\\an8}") - 1);
+ break;
+
+ case 'o':
+ {
+ char otag[5 + sizeof(tags[0].data1) * 2 + 3];
+ int len = sprintf(otag, "{\\pos(%d,%d)}", tags[i].data1, tags[i].data2);
+
+ insert_text_in_subdest(new_line, j, otag, len);
+ break;
+ }
+
+ default:
+ break;
+ }
+ if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
+ tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
+ }
+}
+
+static void microdvd_close_no_persistent_tags(char *new_line, int *j, microdvd_tag_type *tags)
+{
+ int i;
+
+ for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) {
+ if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
+ continue;
+ switch (tags[i].key) {
+ case 'y':
+ {
+ int style_index = sizeof(MICRODVD_STYLES) - 2;
+ char ctag[] = "{\\.0}";
+
+ while (style_index >= 0) {
+ int style_mask = (1 << style_index);
+
+ if (tags[i].data1 & style_mask) {
+ ctag[2] = MICRODVD_STYLES[style_index];
+ insert_text_in_subdest(new_line, j, ctag, sizeof(ctag) - 1);
+ }
+ style_index--;
+ }
+ break;
+ }
+
+ case 'c':
+ insert_text_in_subdest(new_line, j, "{\\c}", sizeof("{\\c}") - 1);
+ break;
+
+ case 'f':
+ insert_text_in_subdest(new_line, j, "{\\fn}", sizeof("{\\fn}") - 1);
+ break;
+
+ case 's':
+ insert_text_in_subdest(new_line, j, "{\\fs}", sizeof("{\\fs}") - 1);
+ break;
+
+ default:
+ break;
+ }
+ tags[i].key = MICRODVD_TAG_DISABLED;
+ }
+}
+
+void subassconvert_microdvd(char *line)
+{
+ int i, j = 0;
+ char new_line[LINE_LEN + 1];
+ char *line_start = line;
+ microdvd_tag_type tags[sizeof(MICRODVD_TAGS) - 1];
+ const microdvd_tag_type dead_tag = MICRODVD_DEAD_TAG;
+
+ for (i = 0; MICRODVD_TAGS[i]; i++)
+ memcpy(&tags[i], &dead_tag, sizeof(dead_tag));
+
+ while (*line) {
+ line = microdvd_load_tags(tags, line);
+ microdvd_open_tags(new_line, &j, tags, MICRODVD_PERSISTENT_ON);
+ microdvd_open_tags(new_line, &j, tags, MICRODVD_PERSISTENT_OFF);
+
+ while (*line && *line != '|')
+ new_line[j++] = *line++;
+
+ if (*line == '|') {
+ microdvd_close_no_persistent_tags(new_line, &j, tags);
+ insert_text_in_subdest(new_line, &j, "\\N", sizeof("\\N") - 1);
+ line++;
+ }
+ }
+
+ new_line[j] = 0;
+ strcpy(line_start, new_line);
+}
diff --git a/subassconvert.h b/subassconvert.h
new file mode 100644
index 0000000..d1be265
--- /dev/null
+++ b/subassconvert.h
@@ -0,0 +1,27 @@
+/*
+ * Header for subtitles converter to SSA/ASS
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_SUBASSCONVERT_H
+#define MPLAYER_SUBASSCONVERT_H
+
+void subassconvert_subrip(char *line);
+void subassconvert_microdvd(char *line);
+
+#endif
diff --git a/subreader.c b/subreader.c
index 394cc41..2075d06 100644
--- a/subreader.c
+++ b/subreader.c
@@ -32,6 +32,8 @@
#include "config.h"
#include "mp_msg.h"
#include "subreader.h"
+#include "subassconvert.h"
+#include "options.h"
#include "stream/stream.h"
#include "libavutil/common.h"
#include "libavutil/avstring.h"
@@ -55,8 +57,6 @@ int fribidi_flip_commas = 0; ///flip comma when fribidi is used
extern char* dvdsub_lang;
-/* Maximal length of line of a subtitle */
-#define LINE_LEN 1000
static float mpsub_position=0;
static float mpsub_multiplier=1.;
static int sub_slacktime = 20000; //20 sec
@@ -291,6 +291,9 @@ static subtitle *sub_read_line_microdvd(stream_t *st,subtitle *current, int utf1
p=line2;
+ if (st->opts->ass_enabled)
+ subassconvert_microdvd(p);
+
next=p, i=0;
while ((next =sub_readtext (next, &(current->text[i])))) {
if (current->text[i]==ERR) {return ERR;}
@@ -373,6 +376,10 @@ static subtitle *sub_read_line_subviewer(stream_t *st,subtitle *current, int utf
for (i=0; i<SUB_MAX_TEXT;) {
int blank = 1;
if (!stream_read_line (st, line, LINE_LEN, utf16)) break;
+
+ if (st->opts->ass_enabled)
+ subassconvert_subrip(line);
+
len=0;
for (p=line; *p!='\n' && *p!='\r' && *p; p++,len++)
if (*p != ' ' && *p != '\t')
@@ -384,16 +391,18 @@ static subtitle *sub_read_line_subviewer(stream_t *st,subtitle *current, int utf
//strncpy (current->text[i], line, len); current->text[i][len]='\0';
for(; j<len; j++) {
/* let's filter html tags ::atmos */
- if(line[j]=='>') {
- skip=0;
- continue;
- }
- if(line[j]=='<') {
- skip=1;
- continue;
- }
- if(skip) {
- continue;
+ if (!st->opts->ass_enabled) {
+ if(line[j]=='>') {
+ skip=0;
+ continue;
+ }
+ if(line[j]=='<') {
+ skip=1;
+ continue;
+ }
+ if(skip) {
+ continue;
+ }
}
*curptr=line[j];
curptr++;
@@ -1352,7 +1361,8 @@ const char* guess_cp(stream_t *st, const char *preferred_language, const char *f
#undef MAX_GUESS_BUFFER_SIZE
#endif
-sub_data* sub_read_file (char *filename, float fps) {
+sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts)
+{
int utf16;
stream_t* fd;
int n_max, n_first, i, j, sub_first, sub_orig;
@@ -1381,6 +1391,7 @@ sub_data* sub_read_file (char *filename, float fps) {
if(filename==NULL) return NULL; //qnx segfault
i = 0;
fd=open_stream (filename, NULL, &i); if (!fd) return NULL;
+ fd->opts = opts;
sub_format = SUB_INVALID;
for (utf16 = 0; sub_format == SUB_INVALID && utf16 < 3; utf16++) {
diff --git a/subreader.h b/subreader.h
index f1706dc..e5704fd 100644
--- a/subreader.h
+++ b/subreader.h
@@ -44,6 +44,9 @@ extern int sub_match_fuzziness;
#define SUB_JACOSUB 12
#define SUB_MPL2 13
+/* Maximal length of line of a subtitle */
+#define LINE_LEN 1000
+
// One of the SUB_* constant above
extern int sub_format;
@@ -84,7 +87,8 @@ extern char *fribidi_charset;
extern int flip_hebrew;
extern int fribidi_flip_commas;
-sub_data* sub_read_file (char *filename, float pts);
+struct MPOpts;
+sub_data* sub_read_file (char *filename, float pts, struct MPOpts *opts);
subtitle* subcp_recode (subtitle *sub);
// enca_fd is the file enca uses to determine the codepage.
// setting to NULL disables enca.
More information about the MPlayer-dev-eng
mailing list