/*********************************************************************** Copyright 2002 Ben Rudiak-Gould. 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-1307 USA, or visit . ***********************************************************************/ #include #include "DVDSubber-compile.h" #include "DVDSubber-format.h" #include "DVDSubber-render.h" #include "DVDSubber-encode.h" #include using namespace std; template static inline T Min(T a, T b) { return a static inline T Max(T a, T b) { return a class DataVector : public DataVectorBase { unsigned n; public: DataVector() { n = 0; } unsigned size() const { return n; } T* begin() { return (T*)data; } T* end() { return (T*)data + n; } void push_back(const T& datum) { if ((n+1)*sizeof(T) > len) grow(); ((T*)data)[n++] = datum; } }; /******************************************************************** ********************************************************************/ const wchar_t UNICODE_REPLACEMENT = 0xFFFD; int UTF8toUTF16(wchar_t* dst, const char* _src) { const unsigned char* src = (const unsigned char*)_src; int dst_pos = 0; for (;;) { unsigned char ch = *src++; if (ch < 0x80) { dst[dst_pos] = ch; if (ch == 0) { break; } ++dst_pos; } else if (ch < 0xC0) { dst[dst_pos++] = UNICODE_REPLACEMENT; } else { unsigned uch = ch; int additional = 0; while (*src >= 0x80 && *src < 0xC0) { uch = (uch << 6) + (*src & 63); ++src; ++additional; } unsigned mask_bits = 32 - 6 - additional*5; uch &= 0xFFFFFFFFU >> mask_bits; unsigned minimum_for_this_encoding = (0x8000000 >> mask_bits); static const unsigned char first_byte_range[] = { 0xC0, 0xE0, 0xF0, 0xF8 }; if (additional < 1 || additional > 3 || ch < first_byte_range[additional-1] || ch >= first_byte_range[additional] || uch < 0x80 || uch < minimum_for_this_encoding || (uch >= 0xD800 && uch < 0xE000) || uch > 0x10FFFF) { uch = UNICODE_REPLACEMENT; } if (uch < 0x10000) { dst[dst_pos] = (wchar_t)uch; } else { uch -= 0x10000; dst[dst_pos++] = 0xD800 + (uch >> 10); dst[dst_pos] = 0xDC00 + (uch & 1023); } ++dst_pos; } } return dst_pos; } /******************************************************************** ********************************************************************/ class Subber { struct Macro { Macro* next; int type; char* data; }; struct Macros { Macros* next; char* name; Macro* body; }; Macros* macros; Macro** recording; wchar_t fontname[32]; int size; bool bold, italic; unsigned char textcolor, halocolor; int linespacing; int base_field; int time0, time1, time2, time3; int linealign, boxalign; int boxleft, boxright, boxtop, boxbottom; bool vertical; bool on; bool got_vts, got_font, got_size, got_color, got_textbox, got_at; Drawable* boxen[30]; Drawable* lines; Drawable* line; int ColorLookup(unsigned c) { unsigned alpha = c >> 24; if (alpha == 0) return 0; unsigned rgb = c & 0xFFFFFF; int index = g_compiler_callbacks->ColorLookup(rgb); if (index >= 0) return index + alpha*16; else throw "too many colors"; } void CloseBox() { if (line) { lines = lines ? new_TextLines(lines, line, linealign) : line; line = 0; } if (lines) { Drawable* box = new_TextBox(lines, boxleft, boxtop, boxright, boxbottom, linealign, boxalign, vertical); for (int n=0; ; ++n) { if (boxen[n]) { box = new_TextBoxen(box, boxen[n]); boxen[n] = 0; } else { boxen[n] = box; break; } } lines = 0; } } public: int error_line_no; unsigned error_time; Subber() { error_line_no = 1; error_time = 0; macros = 0; recording = 0; base_field = 0; bold = false; italic = false; linespacing = 0; on = true; got_at = got_textbox = got_color = got_size = got_font = false; memset(boxen, 0, sizeof(boxen)); lines = 0; line = 0; } ~Subber() { for (int i=0; i<30; ++i) { delete boxen[i]; } delete lines; delete line; } int GetSegment(FILE* f, char* buf, int buflen); void ParseText(char* bytes); void ParseCmd(char* bytes); void ParseLineBreak(); void ParseSegment(int type, char* bytebuf); void ParseFile(FILE* f); void CmdText(const wchar_t* text) { if (!(got_vts & got_font & got_size & got_color & got_textbox & got_at)) throw "Must have , , , , , and before the first text"; if (!on) return; FontInfo* font = LoadFont(fontname, size, bold, italic); // cast away const to fix broken gcc wcsdup prototype Drawable* phrase = new_Text(wcsdup((wchar_t*)text), font, textcolor, halocolor, linespacing, time0, time1, time2, time3); line = line ? new_TextLine(line, phrase) : phrase; } void CmdLineBreak() { if (!on) return; if (!line) { CmdText(L""); } lines = lines ? new_TextLines(lines, line, linealign) : line; line = 0; } void CmdVTS(const int* pvtsn, const wchar_t*) { g_compiler_callbacks->SetVTSNumber(*pvtsn); got_vts = true; } void CmdAngle(const int* pangle, const wchar_t*) { g_compiler_callbacks->SetAngle(*pangle); } void CmdFont(const int*, const wchar_t* fontname) { // check length! if (wcslen(fontname) >= 32) { throw "font name too long!"; } else { wcscpy(this->fontname, fontname); got_font = true; } } void CmdSize(const int* psize, const wchar_t*) { size = *psize; got_size = true; } void CmdLinespacing(const int* pspacing, const wchar_t*) { linespacing = *pspacing; } void CmdColor(const int* colors, const wchar_t*) { textcolor = ColorLookup(colors[0]); halocolor = ColorLookup(colors[1]); got_color = true; } void CmdItalicOn(const int*, const wchar_t*) { italic = true; } void CmdItalicOff(const int*, const wchar_t*) { italic = false; } void CmdBoldOn(const int*, const wchar_t*) { bold = true; } void CmdBoldOff(const int*, const wchar_t*) { bold = false; } void CmdTextbox(const int* dims, const wchar_t* alignment); void CmdFillbox(const int* args, const wchar_t*) { } void CmdBasefield(const int* pbase, const wchar_t*) { base_field = *pbase; } void At(unsigned fadein_time, unsigned full_time, unsigned fadeout_time, unsigned off_time); void CmdAt(const int* times, const wchar_t*); void CmdFadeat(const int* times, const wchar_t*); void CmdOn(const int*, const wchar_t*) { on = true; } void CmdOff(const int*, const wchar_t*) { on = false; } bool Run(); }; void Subber::CmdTextbox(const int* dims, const wchar_t* alignment) { CloseBox(); boxleft = dims[0]; boxright = dims[1]; boxtop = dims[2]; boxbottom = dims[3]; vertical = false; if (*alignment == L'-') { vertical = true; ++alignment; } if (alignment[0] >= L'1' && alignment[0] <= L'9') { boxalign = alignment[0] - '1'; } else { throw "Bad alignment value"; } switch (alignment[1]) { case 'L': case 'T': linealign = -1; break; case 'C': case 'M': linealign = 0; break; case 'R': case 'B': linealign = +1; break; default: throw "Bad alignment value"; } if (vertical) { int t = boxleft; boxleft = boxtop; boxtop = 720-boxright; boxright = boxbottom; boxbottom = 720-t; static unsigned char rotate[9] = { 2, 5, 8, 1, 4, 7, 0, 3, 6 }; boxalign = rotate[boxalign]; } got_textbox = true; } void Subber::At(unsigned fadein_time, unsigned full_time, unsigned fadeout_time, unsigned off_time) { if (fadein_time > full_time || full_time > fadeout_time || fadeout_time > off_time) { throw "subtitle display times are backwards"; } else if (fadein_time >= off_time) { throw "subtitle on and off times are the same"; } time0 = fadein_time + base_field; time1 = full_time + base_field; time2 = fadeout_time + base_field; time3 = off_time + base_field; got_at = true; } void Subber::CmdAt(const int* times, const wchar_t*) { At(times[0], times[0], times[1], times[1]); } void Subber::CmdFadeat(const int* times, const wchar_t*) { At(times[0], times[1], times[2], times[3]); } bool Subber::Run() { CloseBox(); // merge boxen into one big tree int b; for (b=0; b<30; ++b) { if (boxen[b]) break; } Drawable* all_boxes = boxen[b]; boxen[b] = 0; for (++b; b<30; ++b) { if (boxen[b]) { all_boxes = new_TextBoxen(all_boxes, boxen[b]); boxen[b] = 0; } } if (!all_boxes) { throw "no text to render!"; } boxen[0] = all_boxes; // find all event times int max_events = 4000; unsigned* event_time; unsigned* event_time_end; for (;;) { event_time = new unsigned[max_events]; event_time_end = all_boxes->GetEventTimes(event_time, event_time + max_events); if (event_time_end < event_time + max_events) { break; } else { delete[] event_time; max_events *= 2; continue; } } sort(event_time, event_time_end); // uniq in bits 2..31, taking min of bits 0..1 if (event_time_end > event_time) { unsigned* dst = event_time + 1; for (unsigned* src = event_time + 1; src < event_time_end; ++src) if ((src[0] >> 2) != (src[-1] >> 2)) *dst++ = *src; event_time_end = dst; } int num_events = event_time_end - event_time; bool* event_blank = new bool[num_events]; int e; int blanks = 0; for (e=0; eNothingToDraw(event_time[e] >> 2) : false; blanks += event_blank[e]; } g_compiler_callbacks->NotifySubpictureCount(num_events - blanks); bool ntsc = g_compiler_callbacks->IsNTSC(); bool success = true; for (e=0; e> 2; static unsigned char static_buffer[720*576]; static DrawingBuf buf = { static_buffer, 720, 480, 1, 720 }; buf.left = buf.width; buf.right = 0; buf.top = buf.height; buf.bottom = 0; all_boxes->DrawSelf(event_time[e] >> 2, &buf, 0, 0); if (buf.left >= buf.right || buf.top >= buf.bottom) { continue; } SubpictureBuf subpicture_buf; subpicture_buf.SubpictureOn(0, buf.ptr, buf.left, buf.right, buf.top, buf.bottom, ntsc); if (event_blank[e+1]) { subpicture_buf.SubpictureOff((event_time[e+1] >> 2) - (event_time[e] >> 2), ntsc); } const unsigned char* p = subpicture_buf.GetPtr(); g_compiler_callbacks->AddSubpicture(event_time[e] >> 2, p); if (g_compiler_callbacks->UserAborted()) { success = false; break; } } delete[] event_time; delete[] event_blank; return success; } struct Cmd { const char* name; const char* args; void (Subber::*func) (const int* intargs, const wchar_t* stringarg); }; Cmd cmds[] = { { "font ", "s", &Subber::CmdFont }, { "size ", "u", &Subber::CmdSize }, { "linespacing ", "i", &Subber::CmdLinespacing }, { "color ", "xx", &Subber::CmdColor }, { "textbox ", "iiiis", &Subber::CmdTextbox }, { "fillbox ", "uuuux", &Subber::CmdFillbox }, { "vts ", "u", &Subber::CmdVTS }, { "angle ", "u", &Subber::CmdAngle }, { "basefield ", "u", &Subber::CmdBasefield }, { "at ", "uu", &Subber::CmdAt }, { "fadeat ", "uuuu", &Subber::CmdFadeat }, { "i", "", &Subber::CmdItalicOn }, { "/i", "", &Subber::CmdItalicOff }, { "b", "", &Subber::CmdBoldOn }, { "/b", "", &Subber::CmdBoldOff }, { "on", "", &Subber::CmdOn }, { "off", "", &Subber::CmdOff }, }; // type: 0=eof, 1=text, 2=<>, 3=| int Subber::GetSegment(FILE* f, char* buf, int buflen) { int ch = getc(f); if (ch == EOF) { return 0; } else if (ch == '|') { return 3; } else if (ch == '<') { int bufpos = 0; while ((ch = getc(f)) != '>') { if (ch == EOF) { throw "\"<\" without matching \">\" at end of file"; } if (ch == '\n') { throw "\"<\" without matching \">\""; } if (bufpos >= buflen-1) { throw "Command too long"; } buf[bufpos++] = (ch == '\\') ? getc(f) : ch; } buf[bufpos] = 0; return 2; } else { int bufpos = 0; for (;;) { if (ch == '#') { do { ch = getc(f); } while (ch != EOF && ch != '\n'); } if (ch == '\n') { ++error_line_no; } else if (ch == EOF || ch == '<' || ch == '|') { break; } else { if (bufpos >= buflen-1) { throw "Text too long"; } buf[bufpos++] = (ch == '\\') ? getc(f) : ch; } ch = getc(f); } if (ch != EOF) { ungetc(ch, f); } buf[bufpos] = 0; return 1; } } void Subber::ParseText(char* bytes) { if (recording) { Macro* m = new Macro; m->type = 1; m->data = strdup(bytes); *recording = m; recording = &m->next; } else { wchar_t unibuf[1024]; UTF8toUTF16(unibuf, bytes); if (unibuf[0] == 0 || (unibuf[0] == 0xFEFF && unibuf[1] == 0)) { return; } CmdText(unibuf); } } void Subber::ParseCmd(char* bytes) { if (bytes[0] == 0) return; if (memcmp(bytes, "def ", 4) == 0) { if (recording) { throw "nested def"; } else { Macros* m = new Macros; m->next = macros; macros = m; m->name = strdup(bytes+4); recording = &m->body; } } else if (memcmp(bytes, "enddef", 7) == 0) { if (recording) { *recording = 0; recording = 0; } else { throw "enddef without def"; } } else if (recording) { Macro* m = new Macro; m->type = 2; m->data = strdup(bytes); *recording = m; recording = &m->next; } else { for (Macros* m = macros; m; m = m->next) { if (strcmp(m->name, bytes) == 0) { for (Macro* c = m->body; c; c = c->next) { ParseSegment(c->type, c->data); } return; } } for (int c = 0; c < sizeof(cmds)/sizeof(cmds[0]); ++c) { if (memcmp(bytes, cmds[c].name, strlen(cmds[c].name)) == 0) { int int_args[8]; wchar_t unibuf[1024]; unibuf[0] = 0; const char* p = bytes + strlen(cmds[c].name); char* q; for (int a = 0; cmds[c].args[a]; ++a) { if (a > 0) { if (*p++ != ',') throw "expected comma"; } switch (cmds[c].args[a]) { case 'i': int_args[a] = strtol(p, &q, 10); break; case 'u': int_args[a] = strtoul(p, &q, 10); break; case 'x': int_args[a] = strtoul(p, &q, 16); break; case 's': UTF8toUTF16(unibuf, p); q = strchr(p, 0); break; } if (p == q) throw "invalid number"; p = q; while (*p == ' ') ++p; } if (*p != 0) throw "extra junk"; (this->*cmds[c].func)(int_args, unibuf); return; } } puts(bytes); throw "Bad command"; } } void Subber::ParseLineBreak() { if (recording) { Macro* m = new Macro; m->type = 3; *recording = m; recording = &m->next; } else { CmdLineBreak(); } } void Subber::ParseSegment(int type, char* bytebuf) { switch (type) { case 1: ParseText(bytebuf); break; case 2: ParseCmd(bytebuf); break; case 3: ParseLineBreak(); break; } } void Subber::ParseFile(FILE* f) { char bytebuf[1024]; for (;;) { int type = GetSegment(f, bytebuf, sizeof(bytebuf)); if (type == 0) break; ParseSegment(type, bytebuf); } } bool CompileSubtitles(FILE* subfile, ICompilerCallbacks* compiler_callbacks) { g_compiler_callbacks = compiler_callbacks; compiler_callbacks->Note("Reading the subtitle script..."); Subber subber; try { subber.ParseFile(subfile); } catch (const char* error) { compiler_callbacks->ErrorAtLine(error, subber.error_line_no); return false; } compiler_callbacks->NotifyDoneParsing(); compiler_callbacks->Note("Rendering the subtitles..."); try { return subber.Run(); } catch (const char* error) { compiler_callbacks->ErrorAtTime(error, subber.error_time); return false; } }