sigmaplayer/src/subtitle.cpp

1417 lines
34 KiB
C++

//////////////////////////////////////////////////////////////////////////
/**
* SigmaPlayer source project - Subtitles for video player source file.
* \file subtitle.cpp
* \author bombur
* \version 0.1
* \date 01.10.2008
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*/
//////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <libsp/sp_misc.h>
#include <libsp/sp_fip.h>
#include <libsp/sp_khwl.h>
#include <libsp/sp_mpeg.h>
#include <libsp/sp_cdrom.h>
#include "script.h"
#include "script-internal.h"
#include "subtitle.h"
static const int max_allowed_playlist_subs = 2;
static Subtitles *sub = NULL;
static const SubtitleFormat sub_formats[] =
{
{ /* 0 */
"SubRip",
{ SUB(SUB_TOKEN_SKIP_DIGITS), "\n", SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), SUB(SUB_TOKEN_OPTIONAL_NEXT), ",", SUB(SUB_TOKEN_OPTIONAL_NEXT), SUB(SUB_TOKEN_MSEC1), " --> ",
SUB(SUB_TOKEN_HOUR2), ":", SUB(SUB_TOKEN_MIN2), ":", SUB(SUB_TOKEN_SEC2), SUB(SUB_TOKEN_OPTIONAL_NEXT), ",", SUB(SUB_TOKEN_OPTIONAL_NEXT), SUB(SUB_TOKEN_MSEC2),
NULL
},
"\n\n"
},
{ /* 1 */
"SubViewer 1.0",
{ "[", SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), "]\n",
NULL
},
"\n"
},
{ /* 2 */
"SubViewer 2.0",
{ SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), ".", SUB(SUB_TOKEN_DSEC1), ",",
SUB(SUB_TOKEN_HOUR2), ":", SUB(SUB_TOKEN_MIN2), ":", SUB(SUB_TOKEN_SEC2), ".", SUB(SUB_TOKEN_DSEC2), "\n",
NULL
},
"\n\n"
},
{ /* 3 */
"DVDSubtitle",
{ "{T ", SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), ":", SUB(SUB_TOKEN_DSEC1), "\n",
NULL
},
"\n}\n"
},
{ /* 4 */
"DVD Architect",
{ SUB(SUB_TOKEN_SKIP_DIGITS_4), "\t", SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), ":", SUB(SUB_TOKEN_DSEC1), "\t",
SUB(SUB_TOKEN_HOUR2), ":", SUB(SUB_TOKEN_MIN2), ":", SUB(SUB_TOKEN_SEC2), ":", SUB(SUB_TOKEN_DSEC2), "\t",
NULL
},
"\n\n"
},
{ /* 5 */
"MicroDVD",
{ "{", SUB(SUB_TOKEN_FRAMES1), "}{", SUB(SUB_TOKEN_FRAMES2), "}",
NULL
},
"\n"
},
{ /* 6 */
"MPSub",
{ SUB(SUB_TOKEN_DELTA_SECS1), SUB(SUB_TOKEN_OPTIONAL_NEXT), ".", SUB(SUB_TOKEN_OPTIONAL_NEXT), SUB(SUB_TOKEN_DELTA_MSECS1), " ",
SUB(SUB_TOKEN_DELTA_SECS2), SUB(SUB_TOKEN_OPTIONAL_NEXT), ".", SUB(SUB_TOKEN_OPTIONAL_NEXT), SUB(SUB_TOKEN_DELTA_MSECS2), "\n",
NULL
},
"\n\n"
},
{ /* 7 */
"TMPlayer",
{ SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), ",", SUB(SUB_TOKEN_LINE_NUMBER), "=",
NULL
},
"\n"
},
{ /* 8 */
"SubSonic",
{ "1 ", SUB(SUB_TOKEN_SEC1_256), ".", SUB(SUB_TOKEN_DSEC1_256), SUB(SUB_TOKEN_OPTIONAL_NEXT_2), " \\ ~:\\",
NULL
},
"\n"
},
{ /* 9 */
"SubStation Alpha",
{ SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", SUB(SUB_TOKEN_HOUR1_1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), ".", SUB(SUB_TOKEN_DSEC1), ",",
SUB(SUB_TOKEN_HOUR2_1), ":", SUB(SUB_TOKEN_MIN2), ":", SUB(SUB_TOKEN_SEC2), ".", SUB(SUB_TOKEN_DSEC2), ",",
SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", SUB(SUB_TOKEN_SKIP_TO_NEXT), ",",
SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", SUB(SUB_TOKEN_SKIP_TO_NEXT), ",",
NULL
},
"\n"
},
{
NULL,
{ NULL },
NULL
}
};
/////////////////////////////////////////////////////////////////////
BOOL SubtitleData::Alloc(int add_size)
{
if (add_size > 0 && ptr != NULL)
{
ptr = (char *)SPrealloc((void *)ptr, max_string_length + add_size);
left += add_size;
}
else
ptr = cur = (char *)SPmalloc(max_string_length);
if (ptr == NULL)
return FALSE;
cur[0] = '\0';
return TRUE;
}
/////////////////////////////////////////////////////////////////////
Subtitles::Subtitles()
{
Reset();
saved_left[0] = saved_left[1] = 0;
max_line_letters_cnt = 35;
cur_sub = -1;
}
Subtitles::~Subtitles()
{
data.DeleteObjects();
subs.DeleteObjects();
}
void Subtitles::Reset()
{
fd = -1;
cur_method = -1;
buf_idx = 0;
buf_cnt[0] = buf_cnt[1] = 0;
buf_read_left[0] = buf_read_left[1] = 0;
buf_read_cnt = 1;
is_start_of_file = true;
is_utf8 = false; is_utf16 = false; is_utf16be = false;
}
void Subtitles::InitTranslationTable(SUBTITLE_CHARSET charset)
{
static const BYTE koi_win[][2] =
{
{ 0xe1, 0xc0 }, { 0xe2, 0xc1 }, { 0xf7, 0xc2 }, { 0xe7, 0xc3 },
{ 0xe4, 0xc4 }, { 0xe5, 0xc5 }, { 0xf6, 0xc6 }, { 0xfa, 0xc7 },
{ 0xe9, 0xc8 }, { 0xea, 0xc9 }, { 0xeb, 0xca }, { 0xec, 0xcb },
{ 0xed, 0xcc }, { 0xee, 0xcd }, { 0xef, 0xce }, { 0xf0, 0xcf },
{ 0xf2, 0xd0 }, { 0xf3, 0xd1 }, { 0xf4, 0xd2 }, { 0xf5, 0xd3 },
{ 0xe6, 0xd4 }, { 0xe8, 0xd5 }, { 0xe3, 0xd6 }, { 0xfe, 0xd7 },
{ 0xfb, 0xd8 }, { 0xfd, 0xd9 }, { 0xff, 0xda }, { 0xf9, 0xdb },
{ 0xf8, 0xdc }, { 0xfc, 0xdd }, { 0xe0, 0xde }, { 0xf1, 0xdf },
{ 0xc1, 0xe0 }, { 0xc2, 0xe1 }, { 0xd7, 0xe2 }, { 0xc7, 0xe3 },
{ 0xc4, 0xe4 }, { 0xc5, 0xe5 }, { 0xd6, 0xe6 }, { 0xda, 0xe7 },
{ 0xc9, 0xe8 }, { 0xca, 0xe9 }, { 0xcb, 0xea }, { 0xcc, 0xeb },
{ 0xcd, 0xec }, { 0xce, 0xed }, { 0xcf, 0xee }, { 0xd0, 0xef },
{ 0xd2, 0xf0 }, { 0xd3, 0xf1 }, { 0xd4, 0xf2 }, { 0xd5, 0xf3 },
{ 0xc6, 0xf4 }, { 0xc8, 0xf5 }, { 0xc3, 0xf6 }, { 0xde, 0xf7 },
{ 0xdb, 0xf8 }, { 0xdd, 0xf9 }, { 0xdf, 0xfa }, { 0xd9, 0xfb },
{ 0xd8, 0xfc }, { 0xdc, 0xfd }, { 0xc0, 0xfe }, { 0xd1, 0xff },
{ 0xb3, 0xa8 }, { 0xa3, 0xb8 }, { 0, 0 },
};
for (int i = 0; i < 256; i++)
charset_translation_table[i] = (BYTE)i;
if (charset == SUBTITLE_CHARSET_KOI8R) // KOI8 to CP1251
{
for (int i = 0; koi_win[i][0] != '\0'; i++)
charset_translation_table[koi_win[i][0]] = koi_win[i][1];
}
}
void Subtitles::FillUTFTable(SUBTITLE_CHARSET charset)
{
const static WORD utf_win1251[128] =
{
0x0402,0x0403,0x201A,0x0453,0x201E,0x2026,0x2020,0x2021,0x20AC,0x2030,0x0409,0x2039,0x040A,0x040C,0x040B,0x040F,
0x0452,0x2018,0x2019,0x201C,0x201D,0x2022,0x2013,0x2014,0x0000,0x2122,0x0459,0x203A,0x045A,0x045C,0x045B,0x045F,
0x00A0,0x040E,0x045E,0x0408,0x00A4,0x0490,0x00A6,0x00A7,0x0401,0x00A9,0x0404,0x00AB,0x00AC,0x00AD,0x00AE,0x0407,
0x00B0,0x00B1,0x0406,0x0456,0x0491,0x00B5,0x00B6,0x00B7,0x0451,0x2116,0x0454,0x00BB,0x0458,0x0405,0x0455,0x0457,
0x0410,0x0411,0x0412,0x0413,0x0414,0x0415,0x0416,0x0417,0x0418,0x0419,0x041A,0x041B,0x041C,0x041D,0x041E,0x041F,
0x0420,0x0421,0x0422,0x0423,0x0424,0x0425,0x0426,0x0427,0x0428,0x0429,0x042A,0x042B,0x042C,0x042D,0x042E,0x042F,
0x0430,0x0431,0x0432,0x0433,0x0434,0x0435,0x0436,0x0437,0x0438,0x0439,0x043A,0x043B,0x043C,0x043D,0x043E,0x043F,
0x0440,0x0441,0x0442,0x0443,0x0444,0x0445,0x0446,0x0447,0x0448,0x0449,0x044A,0x044B,0x044C,0x044D,0x044E,0x044F,
};
const static WORD utf_iso8859_2[128] =
{
0x0080,0x0081,0x0082,0x0083,0x0084,0x0085,0x0086,0x0087,0x0088,0x0089,0x008A,0x008B,0x008C,0x008D,0x008E,0x008F,
0x0090,0x0091,0x0092,0x0093,0x0094,0x0095,0x0096,0x0097,0x0098,0x0099,0x009A,0x009B,0x009C,0x009D,0x009E,0x009F,
0x00A0,0x0104,0x02D8,0x0141,0x00A4,0x013D,0x015A,0x00A7,0x00A8,0x0160,0x015E,0x0164,0x0179,0x00AD,0x017D,0x017B,
0x00B0,0x0105,0x02DB,0x0142,0x00B4,0x013E,0x015B,0x02C7,0x00B8,0x0161,0x015F,0x0165,0x017A,0x02DD,0x017E,0x017C,
0x0154,0x00C1,0x00C2,0x0102,0x00C4,0x0139,0x0106,0x00C7,0x010C,0x00C9,0x0118,0x00CB,0x011A,0x00CD,0x00CE,0x010E,
0x0110,0x0143,0x0147,0x00D3,0x00D4,0x0150,0x00D6,0x00D7,0x0158,0x016E,0x00DA,0x0170,0x00DC,0x00DD,0x0162,0x00DF,
0x0155,0x00E1,0x00E2,0x0103,0x00E4,0x013A,0x0107,0x00E7,0x010D,0x00E9,0x0119,0x00EB,0x011B,0x00ED,0x00EE,0x010F,
0x0111,0x0144,0x0148,0x00F3,0x00F4,0x0151,0x00F6,0x00F7,0x0159,0x016F,0x00FA,0x0171,0x00FC,0x00FD,0x0163,0x02D9,
};
DWORD i;
for (i = 0; i < 127; i++)
utf16_to_ansi_table[i] = (BYTE)i;
for (i = 127; i < sizeof(utf16_to_ansi_table); i++)
utf16_to_ansi_table[i] = ' ';
if (charset == SUBTITLE_CHARSET_ISO8859_1)
{
for (i = 127; i < 256; i++)
utf16_to_ansi_table[i] = (BYTE)i;
}
else if (charset == SUBTITLE_CHARSET_ISO8859_2)
{
for (i = 0; i < 128; i++)
{
int j = Min((size_t)utf_iso8859_2[i], sizeof(utf16_to_ansi_table));
if (j > 0)
utf16_to_ansi_table[j] = (BYTE)(i + 128);
}
}
else
{
for (i = 0; i < 128; i++)
{
int j = Min((size_t)utf_win1251[i], sizeof(utf16_to_ansi_table));
if (j > 0)
utf16_to_ansi_table[j] = (BYTE)(i + 128);
}
}
if (is_utf8 || is_utf16)
{
for (i = 0; i < 256; i++)
charset_translation_table[i] = (BYTE)i;
}
}
int Subtitles::AddString(BYTE *str, int &length, bool attach)
{
int l = length + 1;
if (l > max_string_length)
return -1;
int n = data.GetN();
SubtitleData *d = data[n - 1];
if (n == 0 || d->left < l)
{
if (!attach || n == 0 || !d->Alloc(l))
{
d = new SubtitleData();
if (!d->Alloc(0))
return -1;
if (data.Add(d) < 0)
return -1;
n = data.GetN();
}
}
char last_ds;
if (attach && d->cur > d->ptr)
{
*(d->cur - 1) = last_ds = '\n';
}
else
last_ds = 0;
// replace all newlines now:
char *dst = d->cur;
int i = 0;
int line_letters_cnt = 0, last_space_cnt = 0;
for (BYTE *s = str; i < length; i++, s++)
{
char ds;
if (*s == '|')
ds = '\n';
else if (*s == '\\' && s[1] == 'N')
{
ds = '\n';
s++;
i++;
}
else if (*s == '[' && s[1] == 'b' && s[2] == 'r' && s[3] == ']')
{
ds = '\n';
s += 3;
i += 3;
}
else
{
ds = charset_translation_table[(BYTE)*s];
if (ds == ' ')
last_space_cnt = line_letters_cnt;
}
if (line_letters_cnt == 0)
{
if (ds == ' ' || ds == '\n' || ds == '%')
{
continue;
}
}
else if (line_letters_cnt > max_line_letters_cnt)
{
if (last_space_cnt > 0)
{
int delta = line_letters_cnt - last_space_cnt;
s -= delta;
i -= delta;
dst -= delta;
line_letters_cnt = last_space_cnt;
last_space_cnt = 0;
ds = '\n';
}
}
if (ds == '\n')
{
line_letters_cnt = 0;
if (last_ds == '\n')
{
continue;
}
}
else
line_letters_cnt++;
*dst++ = last_ds = ds;
}
if (last_ds == '\n' || last_ds == '%')
{
dst--;
}
*dst = '\0';
int pos = d->cur - d->ptr;
length = dst - d->cur + 1;
d->cur += length;
d->len += length;
d->left -= length;
length--;
return pos;
}
BOOL Subtitles::Scan(BYTE *&buf, int &left, int line_num, int method, SubtitleFields fields[2])
{
memset(fields, 0xff, sizeof(SubtitleFields) * 2);
fields[1].is_set = false;
SUB_CUR_MODE cur_mode = SUB_CUR_MODE_NORMAL;
SUB_CUR_FORMAT cur_format = SUB_CUR_FORMAT_STRING;
SUB_CUR_FIELD cur_field = SUB_CUR_FIELD_NONE;
int cur_num_digits = 0;
int field_idx = 0;
const SubtitleFormat &fmt = sub_formats[method];
for (int i = 0; fmt.start[i] != NULL; i++)
{
const char *str = fmt.start[i];
if ((int)str < 0x1000)
{
cur_field = SUB_CUR_FIELD_NONE;
if (str == SUB(SUB_TOKEN_SKIP_TO_NEXT))
{
cur_mode = SUB_CUR_MODE_SEARCH_NEXT;
continue;
}
else if (str == SUB(SUB_TOKEN_OPTIONAL_NEXT))
{
cur_mode = SUB_CUR_MODE_OPTIONAL;
continue;
}
else if (str == SUB(SUB_TOKEN_OPTIONAL_NEXT_2))
{
if (line_num > 0)
cur_mode = SUB_CUR_MODE_OPTIONAL;
continue;
}
if (str >= SUB(SUB_TOKEN_SKIP_DIGITS) && str <= SUB(SUB_TOKEN_SKIP_DIGITS_4))
{
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = str - SUB(SUB_TOKEN_SKIP_DIGITS);
}
field_idx = ((int)str > 0x100) ? 1 : 0;
switch ((int)str & 0xff)
{
case SUB_TOKEN_HOUR1:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 2;
cur_field = SUB_CUR_FIELD_HOUR;
break;
case SUB_TOKEN_HOUR1_1:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 1;
cur_field = SUB_CUR_FIELD_HOUR;
break;
case SUB_TOKEN_MIN1:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 2;
cur_field = SUB_CUR_FIELD_MIN;
break;
case SUB_TOKEN_SEC1:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 2;
cur_field = SUB_CUR_FIELD_SEC;
break;
case SUB_TOKEN_DSEC1:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 2;
cur_field = SUB_CUR_FIELD_MSEC;
break;
case SUB_TOKEN_MSEC1:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 3;
cur_field = SUB_CUR_FIELD_MSEC;
break;
case SUB_TOKEN_SEC1_256:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 0;
cur_field = SUB_CUR_FIELD_SEC256;
break;
case SUB_TOKEN_DSEC1_256:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 2;
cur_field = SUB_CUR_FIELD_MSEC256;
break;
case SUB_TOKEN_DELTA_SECS1:
cur_format = SUB_CUR_FORMAT_DIGITS_SIGN;
cur_num_digits = 0;
cur_field = SUB_CUR_FIELD_DELTA_SEC;
break;
case SUB_TOKEN_DELTA_MSECS1:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 0;
cur_field = SUB_CUR_FIELD_DELTA_MSEC;
break;
case SUB_TOKEN_FRAMES1:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 0;
cur_field = SUB_CUR_FIELD_FRAMES;
break;
case SUB_TOKEN_LINE_NUMBER:
cur_format = SUB_CUR_FORMAT_DIGITS;
cur_num_digits = 1;
cur_field = SUB_CUR_FIELD_LINE_NUMBER;
break;
}
} else
{
cur_format = SUB_CUR_FORMAT_STRING;
cur_field = SUB_CUR_FIELD_NONE;
}
int value = 0, sign = 1;
while (left > 0)
{
int num_read = 0;
SavePos(1, buf, left);
switch (cur_format)
{
case SUB_CUR_FORMAT_STRING:
{
const char *ss = str;
for (; *ss != '\0'; buf++, left--, num_read++)
{
if (left < 1)
{
if ((buf = ReadMore(&left)) == NULL)
{
num_read = 0;
return FALSE;
}
}
if (*buf == '\r')
continue;
if (*buf != *ss)
{
num_read = 0;
if (!LoadPos(1, buf, left))
return FALSE;
break;
}
ss++;
}
}
break;
case SUB_CUR_FORMAT_DIGITS_SIGN:
if (*buf == '-')
{
num_read++;
buf++;
left--;
sign = -1;
if (left < 1)
{
if ((buf = ReadMore(&left)) == NULL)
{
num_read = 0;
return FALSE;
}
}
}
case SUB_CUR_FORMAT_DIGITS:
value = 0;
while (left > 0)
{
int max_num_read = cur_num_digits == 0 ? left : cur_num_digits;
for ( ; num_read < max_num_read && left > 0 && isdigit(*buf); num_read++, buf++, left--)
{
value = value * 10 + (*buf - '0');
}
//if ((cur_num_digits > 0 && num_read == cur_num_digits) || (cur_num_digits == 0 && num_read > 0))
if (left > 0)
break;
if ((buf = ReadMore(&left)) == NULL)
return FALSE;
}
if (cur_num_digits > 0 && num_read != cur_num_digits)
{
num_read = 0;
value = 0;
}
break;
}
if (num_read > 0)
{
value *= sign;
switch (cur_field)
{
case SUB_CUR_FIELD_HOUR:
fields[field_idx].hour = value;
break;
case SUB_CUR_FIELD_MIN:
fields[field_idx].min = value;
break;
case SUB_CUR_FIELD_SEC:
fields[field_idx].sec = value;
break;
case SUB_CUR_FIELD_SEC256:
fields[field_idx].sec256 = value;
break;
case SUB_CUR_FIELD_MSEC:
fields[field_idx].msec = (num_read == 2) ? value * 10 : value;
break;
case SUB_CUR_FIELD_MSEC256:
fields[field_idx].msec256 = (num_read == 2) ? value * 10 : value;
break;
case SUB_CUR_FIELD_DELTA_SEC:
fields[field_idx].delta_sec = value - 1;
break;
case SUB_CUR_FIELD_DELTA_MSEC:
fields[field_idx].delta_msec = ((num_read == 1) ? value * 100 : value * 10) - 1;
break;
case SUB_CUR_FIELD_FRAMES:
fields[field_idx].frames = value;
break;
case SUB_CUR_FIELD_LINE_NUMBER:
fields[field_idx].line_number = value;
break;
case SUB_CUR_FIELD_NONE:
break;
}
if (cur_field != SUB_CUR_FIELD_NONE)
fields[field_idx].is_set = true;
cur_mode = SUB_CUR_MODE_NORMAL;
}
else if (cur_mode == SUB_CUR_MODE_SEARCH_NEXT)
{
// skip only at current line
buf++;
left--;
if (left < 1)
{
if ((buf = ReadMore(&left)) == NULL)
return FALSE;
}
if (*buf == '\n' || left < 1)
return FALSE;
continue;
}
else if (cur_mode == SUB_CUR_MODE_OPTIONAL)
{
LoadPos(1, buf, left);
cur_mode = SUB_CUR_MODE_NORMAL;
break;
}
else
{
// failure - didn't match the template
return FALSE;
}
cur_mode = SUB_CUR_MODE_NORMAL;
break;
}
if (left < 1)
{
buf = sub->ReadMore(&left);
if (buf == NULL)
return FALSE;
}
}
return TRUE;
}
void Subtitles::ConvertUTF16(BYTE *buf, int *left)
{
int l = *left;
BYTE *wb = buf;
BYTE *rb = wb;
for (; l > 0; l-=2, wb++, rb+=2)
{
WORD u = rb[0] | (rb[1] << 8);
*wb = (u < sizeof(utf16_to_ansi_table)) ? utf16_to_ansi_table[u] : ' ';
}
*left = wb - buf;
}
void Subtitles::ConvertUTF16BE(BYTE *buf, int *left)
{
int l = *left;
BYTE *wb = buf;
BYTE *rb = wb;
for (; l > 0; l-=2, wb++, rb+=2)
{
WORD u = rb[1] | (rb[0] << 8);
*wb = (u < sizeof(utf16_to_ansi_table)) ? utf16_to_ansi_table[u] : ' ';
}
*left = wb - buf;
}
int Subtitles::ConvertUTF8(BYTE *buf, int *left)
{
int l = *left - 6;
int d = 1;
BYTE *wb = buf;
BYTE *rb = wb;
for (; l > 0; wb++, rb += d, l -= d)
{
WORD u = 0xffff;
d = 1;
if ((rb[0] & 0x80) == 0x00)
u = rb[0];
else if ((rb[0] & 0xe0) == 0xc0 && (rb[1] & 0xc0) == 0x80)
{
u = ((rb[0] & 0x1fL) << 6) | ((rb[1] & 0x3fL) << 0);
if (u >= 0x00000080L)
d = 2;
}
else if ((rb[0] & 0xf0) == 0xe0 && (rb[1] & 0xc0) == 0x80 && (rb[2] & 0xc0) == 0x80)
{
u = ((rb[0] & 0x0fL) << 12) | ((rb[1] & 0x3fL) << 6) | ((rb[2] & 0x3fL) << 0);
if (u >= 0x00000800L)
d = 3;
}
else if ((rb[0] & 0xf8) == 0xf0 && (rb[1] & 0xc0) == 0x80 && (rb[2] & 0xc0) == 0x80 &&
(rb[3] & 0xc0) == 0x80)
{
u = ((rb[0] & 0x07L) << 18) | ((rb[1] & 0x3fL) << 12) |
((rb[2] & 0x3fL) << 6) | ((rb[3] & 0x3fL) << 0);
if (u >= 0x00010000L)
d = 4;
}
else if ((rb[0] & 0xfc) == 0xf8 && (rb[1] & 0xc0) == 0x80 && (rb[2] & 0xc0) == 0x80 &&
(rb[3] & 0xc0) == 0x80 && (rb[4] & 0xc0) == 0x80)
{
u = ((rb[0] & 0x03L) << 24) | ((rb[1] & 0x3fL) << 18) |
((rb[2] & 0x3fL) << 12) | ((rb[3] & 0x3fL) << 6) | ((rb[4] & 0x3fL) << 0);
if (u >= 0x00200000L)
d = 5;
}
else if ((rb[0] & 0xfe) == 0xfc && (rb[1] & 0xc0) == 0x80 && (rb[2] & 0xc0) == 0x80 &&
(rb[3] & 0xc0) == 0x80 && (rb[4] & 0xc0) == 0x80 && (rb[5] & 0xc0) == 0x80)
{
u = ((rb[0] & 0x01L) << 30) | ((rb[1] & 0x3fL) << 24) | ((rb[2] & 0x3fL) << 18) |
((rb[3] & 0x3fL) << 12) | ((rb[4] & 0x3fL) << 6) | ((rb[5] & 0x3fL) << 0);
if (u >= 0x04000000L)
d = 6;
}
*wb = (u < sizeof(utf16_to_ansi_table)) ? utf16_to_ansi_table[u] : ' ';
}
*left = wb - buf;
return rb - buf;
}
BYTE *Subtitles::ReadMore(int *left, bool force_read)
{
// we don't want to read more data if subtitle format is unknown
if (cur_method < 0 && !force_read)
return NULL;
int new_bufidx = (++buf_idx) & 1;
BYTE *b = buf[new_bufidx];
if (buf_read_cnt <= buf_cnt[new_bufidx])
{
*left = buf_read_left[new_bufidx];
} else
{
int left0 = read(fd, b, read_buf_length);
if (is_start_of_file)
{
if (b[0] == 0xff && b[1] == 0xfe)
{
sub->is_utf16 = true;
msg("Subtitle: UTF-16 Detected!\n");
FillUTFTable(params->info.subtitle_charset);
b += 2;
left0 -= 2;
}
if (b[0] == 0xfe && b[1] == 0xff)
{
sub->is_utf16be = true;
msg("Subtitle: UTF-16BE Detected!\n");
FillUTFTable(params->info.subtitle_charset);
b += 2;
left0 -= 2;
}
else if (b[0] == 0xef && b[1] == 0xbb && b[2] == 0xbf)
{
sub->is_utf8 = true;
msg("Subtitle: UTF-8 Detected!\n");
FillUTFTable(params->info.subtitle_charset);
b += 3;
left0 -= 3;
}
is_start_of_file = false;
}
*left = left0;
// UTF8/16 conversion
if (sub->is_utf16)
{
ConvertUTF16(b, left);
}
if (sub->is_utf16be)
{
ConvertUTF16BE(b, left);
}
else if (sub->is_utf8)
{
left0 -= ConvertUTF8(b, left);
lseek(fd, -left0, SEEK_CUR);
}
buf_read_cnt++;
}
if (*left < 1)
{
if (*left < 0)
{
msg("Subtitle: cannot read file!\n");
}
return NULL;
}
buf_read_left[new_bufidx] = *left;
buf_cnt[new_bufidx] = buf_read_cnt;
buf_idx = new_bufidx;
return b;
}
void Subtitles::SavePos(int saveidx, BYTE *buf, int left)
{
saved_bufidx[saveidx] = buf_idx;
saved_buf[saveidx] = buf;
saved_left[saveidx] = left;
saved_bufcnt[saveidx] = buf_cnt[buf_idx];
}
BOOL Subtitles::LoadPos(int saveidx, BYTE *&buf, int &left)
{
if (buf_cnt[saved_bufidx[saveidx]] != saved_bufcnt[saveidx] || saved_buf[saveidx] == NULL)
return FALSE;
buf_idx = saved_bufidx[saveidx];
buf = saved_buf[saveidx];
left = saved_left[saveidx];
return TRUE;
}
///////////////////////////////////////////////////////////////
void Subtitle::Add(BYTE *str, int str_length, SubtitleFields result[2])
{
if (str_length > 0)
sub->AddString(str, str_length, false);
SubtitleRecord rec;
rec.string_length = (WORD)str_length;
rec.delta_time = GetTime(result[0]);
records.Add(rec);
if (result[1].is_set)
{
SubtitleRecord rec;
rec.string_length = 0;
rec.delta_time = GetTime(result[1]);
records.Add(rec);
}
}
short Subtitle::GetTime(const SubtitleFields & f)
{
int new_time = cur_time;
if (f.frames >= 0)
{
new_time = (int)(INT64(100000) * (LONGLONG)f.frames / fps);
}
else
{
if (f.sec256 >= 0 && f.msec256 >= 0)
{
new_time = f.sec256 * 100 + f.msec256 / 10;
if (new_time < last_256 || new_time > last_256 + 25600)
last_256_add += 25600;
last_256 = new_time;
new_time += last_256_add;
}
if (f.delta_sec != -1)
{
new_time += (f.delta_sec + 1) * 100;
}
if (f.delta_msec != -1)
{
new_time += (f.delta_msec + 1) / 10;
}
if (f.sec >= 0)
{
new_time = f.hour * 360000 + f.min * 6000 + f.sec * 100 + f.msec / 10;
}
}
int delta = new_time - cur_time;
cur_time = new_time;
return (short)delta;
}
/////////////////////////////////////////////////////////////////////
int SimilarityCompare(SPString *str1, SPString *str2)
{
// this is a little bit slow
SPString s[2];
s[0] = *str1;
s[1] = *str2;
for (int i = 0; i < 2; i++)
{
int name_pos = s[i].ReverseFind('/');
if (name_pos > 0)
s[i] = s[i].Mid(name_pos + 1);
name_pos = s[i].ReverseFind('.');
if (name_pos >= 0)
s[i] = s[i].Left(name_pos);
}
return sub->vid_file.Similar(s[1]) - sub->vid_file.Similar(s[0]);
}
BOOL read_subtitles(char *video_fname)
{
static const char *subt_ext[] = { ".sub", ".srt", ".ssa", ".txt", NULL };
int i;
SPSafeDelete(sub);
sub = new Subtitles;
if (params == NULL)
return FALSE;
fip_write_string("LoAd");
sub->InitTranslationTable(params->info.subtitle_charset);
sub->max_line_letters_cnt = params->info.subtitle_wrap;
// now add subtitle files
for (i = 0; i < params->playlists.GetN(); i++)
{
if (params->playlists[i] == NULL)
continue;
SPList<Item *> &il = params->playlists[i]->items;
for (int j = 0; j < il.GetN(); j++)
{
SPString &name = il[j]->name;
// check extensions
bool found = false;
for (int k = 0; subt_ext[k] != NULL; k++)
{
if (name.FindNoCase(subt_ext[k]) == name.GetLength() - 4)
{
found = true;
break;
}
}
if (found)
{
if (sub->subs.GetN() < max_allowed_playlist_subs)
{
add_subtitle_file(name);
// enable first user-selected subtitle file by default
if (sub->subs.GetN() > 0)
sub->cur_sub = 0;
}
// now remove item from the playlist
if (params->playlists[i]->itemhash != NULL)
params->playlists[i]->itemhash->Remove(*il[j]);
SPSafeDelete(il[j]);
il.Remove(j);
j--;
}
}
}
//!!!!!!!!!!!!!!!!!!!!
//add_subtitle_file("/cdrom/SUB/MicroDVD.sub");
//add_subtitle_file("/cdrom/SUB/MPSub.sub");
//add_subtitle_file("/cdrom/SUB/DVDSubtitle.sub");
//add_subtitle_file("/cdrom/SUB/SubViewer 1.0.sub");
//add_subtitle_file("/cdrom/SUB/SubSonic.sub");
//add_subtitle_file("/cdrom/SUB/SubRip.srt");
//add_subtitle_file("/cdrom/SUB/LOTR/DVD Architect.sub");
//add_subtitle_file("/cdrom/SUB/LOTR/DVDSubtitle.sub");
//add_subtitle_file("/cdrom/SUB/LOTR/MicroDVD.sub");
//add_subtitle_file("/cdrom/SUB/LOTR/MPSub.sub");
//add_subtitle_file("/cdrom/SUB/LOTR/SubRip.srt");
//add_subtitle_file("/cdrom/SUB/LOTR/SubSonic.sub");
//add_subtitle_file("/cdrom/SUB/LOTR/SubStation Alpha.ssa");
//add_subtitle_file("/cdrom/SUB/LOTR/SubViewer1.sub");
//add_subtitle_file("/cdrom/SUB/LOTR/SubViewer2.sub");
//add_subtitle_file("/cdrom/SUB/LOTR/TMPlayer.sub");
//add_subtitle_file("/cdrom/Scary_movie_RUS_2000.srt");
// try adding curdir subtitle, if there's only one (and it wasn't added)
if (sub->subs.GetN() == 0)
{
SPClassicList<SPString> files;
DIR *dir = cdrom_opendir(params->folder);
SPString name, path = params->folder;
if (path.ReverseFind('/') != path.GetLength() - 1)
path += "/";
while (dir != NULL)
{
struct dirent *d = cdrom_readdir(dir);
if (d == NULL)
break;
if ((d->d_name[0] == '.' && d->d_name[1] == '\0') ||
(d->d_name[0] == '.' && d->d_name[1] == '.' && d->d_name[2] == '\0'))
continue;
struct stat64 statbuf;
name = path + d->d_name;
if (cdrom_stat(*name, &statbuf) < 0)
{
msg("Subtitle: Cannot stat %s\n", *name);
statbuf.st_mode = 0;
//continue;
}
if (S_ISDIR(statbuf.st_mode))
continue;
// is it subtitle file?
bool found = false;
for (int k = 0; subt_ext[k] != NULL; k++)
{
if (name.FindNoCase(subt_ext[k]) == name.GetLength() - 4)
{
found = true;
break;
}
}
if (found)
{
found = false;
for (int i = 0; i < sub->subs.GetN(); i++)
{
if (sub->subs[i]->fname.CompareNoCase(name) == 0)
{
found = true;
break;
}
}
if (!found)
files.Add(name);
}
}
cdrom_closedir(dir);
sub->vid_file = video_fname;
int name_pos = sub->vid_file.ReverseFind('/');
if (name_pos > 0)
sub->vid_file = sub->vid_file.Mid(name_pos + 1);
name_pos = sub->vid_file.ReverseFind('.');
if (name_pos >= 0)
sub->vid_file = sub->vid_file.Left(name_pos);
// now sort and cut the list
files.Sort(SimilarityCompare);
for (int i = 0; i < files.GetN() && sub->subs.GetN() < max_allowed_playlist_subs; i++)
{
add_subtitle_file(files[i]);
}
}
return TRUE;
}
BOOL delete_subtitles()
{
SPSafeDelete(sub);
return TRUE;
}
BOOL add_subtitle_file(char *fname)
{
if (sub == NULL)
return FALSE;
msg("Subtitle: Reading %s...\n", fname);
sub->Reset();
sub->fd = cdrom_open(fname, O_RDONLY);
if (sub->fd < 0)
{
msg("Subtitle: cannot open file %s\n", fname);
return FALSE;
}
int line_num = 0;
Subtitle *news = NULL;
sub->cur_method = -1;
bool detected = false;
BYTE str_buf[4096];
for (;;)
{
int left = 0;
BYTE *buf = sub->ReadMore(&left, !detected);
if (buf == NULL)
break;
while (left > 0)
{
int method = MAX(sub->cur_method, 0);
SubtitleFields result[2];
bool found = false;
do
{
sub->SavePos(0, buf, left);
if (sub->Scan(buf, left, line_num, method, result))
{
found = true;
break;
}
// try again
if (sub->cur_method < 0)
{
if (!sub->LoadPos(0, buf, left))
{
close(sub->fd);
return FALSE;
}
}
method++;
} while (sub->cur_method < 0 && sub_formats[method].name != NULL);
// skip or need more data?
if (!found)
{
do
{
while (left > 0 && *buf != '\n')
{
buf++;
left--;
}
} while (left < 1 && (buf = sub->ReadMore(&left)) != NULL);
do
{
while (left > 0 && (*buf == '\n' || *buf == '\r'))
{
buf++;
left--;
}
} while (left < 1 && (buf = sub->ReadMore(&left)) != NULL);
continue;
}
sub->cur_method = method;
// add subtitle to the list
if (!detected)
{
news = new Subtitle();
news->fname = fname;
news->name = fname;
int name_pos = news->name.ReverseFind('/');
if (name_pos > 0)
news->name = news->name.Mid(name_pos + 1);
name_pos = news->name.ReverseFind('.');
if (name_pos >= 0)
news->name = news->name.Left(name_pos);
news->name = news->name.Left(10);
if (sub->data.GetN() > 0)
{
news->data_idx = sub->data.GetN() - 1;
SubtitleData *d = sub->data[news->data_idx];
news->data_start_pos = d->cur - d->ptr;
} else
{
news->data_idx = 0;
news->data_start_pos = 0;
}
news->cur_data_idx = news->data_idx;
news->cur_data_pos = news->data_start_pos;
sub->subs.Add(news);
detected = true;
msg("Subtitle: * %s format detected!\n", sub_formats[sub->cur_method].name);
}
// now read the string
BYTE *str = str_buf;
const char *end = sub_formats[method].end;
int str_length = 0;
found = false;
while (left > 0 && str_length < 4096)
{
found = true;
sub->SavePos(1, buf, left);
const char *e = end;
do
{
for (; *e != '\0' && left > 0; buf++, left--)
{
if (*buf == '\r')
continue;
if (*buf != *e)
{
found = false;
break;
}
e++;
}
} while (left < 1 && (buf = sub->ReadMore(&left)) != NULL);
if (buf == NULL)
break;
if (found)
break;
sub->LoadPos(1, buf, left);
if (*buf != '\r')
{
*str++ = *buf;
str_length++;
}
buf++;
left--;
if (left < 1)
{
if ((buf = sub->ReadMore(&left)) == NULL)
{
close(sub->fd);
return FALSE;
}
}
}
if (news->fps == 0 && result[0].frames > 0)
{
char buf[128];
int l = MIN(str_length, 100);
strncpy(buf, (const char *)str_buf, l);
buf[l] = '\0';
int f1 = 0, f2 = 0;
sscanf(buf, "%d.%d", &f1, &f2);
news->fps = f1 * 1000 + f2;
continue;
}
if (result[0].line_number > 1)
{
// attach to previous string
if (str_length > 0)
{
sub->AddString(str_buf, str_length, true);
SubtitleRecord &rec = news->records[news->records.GetN() - 1];
rec.string_length = (WORD)(rec.string_length + str_length + 1);
}
} else
{
news->Add(str_buf, str_length, result);
line_num = (line_num + 1) & 1;
}
}
// if didn't guess, no need to read more...
if (sub->cur_method < 0)
{
msg("Subtitle: Error! Unknown subtitle file format.\n");
close(sub->fd);
return FALSE;
}
}
news->is_ready = true;
if (news->records.GetN() > 0)
{
news->cur_pts = news->records[0].delta_time * 900;
news->cur_record = 0;
}
close(sub->fd);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#if 0
{
int sub_idx = 0;
if (sub->subs.GetN() > sub_idx && sub->subs[sub_idx]->is_ready)
{
int pi = sub->subs[sub_idx]->data_idx, pp = sub->subs[sub_idx]->data_start_pos, t = 0;
for (int z = 0; z < sub->subs[sub_idx]->records.GetN(); z++)
{
int l = sub->subs[sub_idx]->records[z].string_length;
char *s;
t += sub->subs[sub_idx]->records[z].delta_time;
if (l > 0)
{
s = sub->data[pi]->ptr + pp;
pp += l + 1;
}
else
s = "";
if (sub->data.GetN() > 0)
if (pp >= sub->data[pi]->len)
{
if (pi < sub->data.GetN() - 1)
pi++;
pp = 0;
}
}
}
}
#endif
return TRUE;
}
BOOL show_subtitles(LONGLONG pts)
{
if (sub == NULL || sub->cur_sub < 0 || pts < 0)
return FALSE;
Subtitle *cur = sub->subs[sub->cur_sub];
const char *s = NULL;
if (pts > cur->cur_pts)
{
for (int z = cur->cur_record; z < cur->records.GetN(); z++)
{
SubtitleRecord &r = cur->records[z];
LONGLONG d_pts = (LONGLONG)((int)r.delta_time * 900);
if (z != cur->cur_record)
{
cur->cur_pts += d_pts;
cur->cur_record = z;
if (pts < cur->cur_pts)
break;
}
cur->cur_last_pts = cur->cur_pts;
cur->cur_last_record = z;
int l = cur->records[cur->cur_record].string_length;
if (l > 0 && sub->data.GetN() > 0)
{
s = sub->data[cur->cur_data_idx]->ptr + cur->cur_data_pos;
cur->cur_data_pos += l + 1;
if (cur->cur_data_pos >= sub->data[cur->cur_data_idx]->len)
{
if (cur->cur_data_idx < sub->data.GetN() - 1)
cur->cur_data_idx++;
cur->cur_data_pos = 0;
}
}
else
s = "";
}
}
else if (pts < cur->cur_last_pts && cur->cur_last_record > 0)
{
int z;
for (z = cur->cur_last_record; z >= 0; z--)
{
SubtitleRecord &r = cur->records[z];
LONGLONG d_pts = (LONGLONG)((int)r.delta_time * 900);
cur->cur_pts = cur->cur_last_pts;
cur->cur_record = cur->cur_last_record;
cur->cur_last_record--;
cur->cur_last_pts -= d_pts;
int l = cur->records[z].string_length;
if (l > 0 && sub->data.GetN() > 0)
{
cur->cur_data_pos -= l + 1;
if (cur->cur_data_pos < 0)
{
if (cur->cur_data_idx > 0)
{
cur->cur_data_idx--;
cur->cur_data_pos = sub->data[cur->cur_data_idx]->len - (l + 1);
} else
cur->cur_data_pos = 0;
}
}
if (pts > cur->cur_last_pts)
break;
}
if (z < 0 || cur->cur_last_pts == 0)
{
cur->cur_last_pts = 0;
cur->cur_last_record = -1;
if (cur->records.GetN() > 0)
{
cur->cur_pts = cur->records[0].delta_time * 900;
cur->cur_record = 0;
}
}
// don't show subtitles in reverse mode
s = "";
}
if (s != NULL)
{
int t = (int)(pts / 900);
msg("[%02d:%02d:%02d.%02d] %s\n\n", t/360000, (t % 360000) / 6000, (t % 6000) / 100, (t % 100), s);
script_player_subtitle_callback(s);
return TRUE;
}
return FALSE;
}
BOOL subtitle_next()
{
if (sub == NULL || sub->subs.GetN() < 1 || params == NULL)
return FALSE;
if (++sub->cur_sub >= sub->subs.GetN())
sub->cur_sub = -1;
params->info.spu_stream = sub->cur_sub + 1;
if (sub->cur_sub >= 0)
{
params->info.spu_lang = sub->subs[sub->cur_sub]->name;
msg("Subtitle: * Set to '%s'.\n", *params->info.spu_lang);
}
else
{
params->info.spu_lang = SPString();
msg("Subtitle: * Disabled.\n");
}
script_player_subtitle_callback("");
return TRUE;
}