2006-12-28 22:18:35 +01:00
|
|
|
// Copyright (c) 2006, Niels Martin Hansen
|
|
|
|
// All rights reserved.
|
|
|
|
//
|
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
|
|
// modification, are permitted provided that the following conditions are met:
|
|
|
|
//
|
|
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
|
|
// this list of conditions and the following disclaimer.
|
|
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
|
|
// and/or other materials provided with the distribution.
|
|
|
|
// * Neither the name of the Aegisub Group nor the names of its contributors
|
|
|
|
// may be used to endorse or promote products derived from this software
|
|
|
|
// without specific prior written permission.
|
|
|
|
//
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
//
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// AEGISUB
|
|
|
|
//
|
|
|
|
// Website: http://aegisub.cellosoft.com
|
|
|
|
// Contact: mailto:jiifurusu@gmail.com
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "auto4_lua.h"
|
|
|
|
#include "ass_dialogue.h"
|
|
|
|
#include "ass_style.h"
|
|
|
|
#include "ass_file.h"
|
|
|
|
#include "ass_override.h"
|
2007-01-03 20:23:18 +01:00
|
|
|
#include "../lua51/src/lualib.h"
|
|
|
|
#include "../lua51/src/lauxlib.h"
|
2006-12-28 22:18:35 +01:00
|
|
|
#include <assert.h>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
namespace Automation4 {
|
|
|
|
|
|
|
|
// LuaAssFile
|
|
|
|
|
|
|
|
void LuaAssFile::CheckAllowModify()
|
|
|
|
{
|
|
|
|
if (can_modify)
|
|
|
|
return;
|
|
|
|
lua_pushstring(L, "Attempt to modify subtitles in read-only feature context.");
|
|
|
|
lua_error(L);
|
|
|
|
}
|
|
|
|
|
|
|
|
void LuaAssFile::AssEntryToLua(lua_State *L, AssEntry *e)
|
|
|
|
{
|
|
|
|
lua_newtable(L);
|
|
|
|
|
|
|
|
wxString section(e->group);
|
|
|
|
lua_pushstring(L, section.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "section");
|
|
|
|
|
|
|
|
wxString raw(e->GetEntryData());
|
|
|
|
lua_pushstring(L, raw.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "raw");
|
|
|
|
|
|
|
|
if (raw.Trim().IsEmpty()) {
|
|
|
|
lua_pushstring(L, "clear");
|
|
|
|
|
|
|
|
} else if (raw[0] == _T(';')) {
|
|
|
|
// "text" field, same as "raw" but with semicolon stripped
|
|
|
|
wxString text(raw, 1, raw.size()-1);
|
|
|
|
lua_pushstring(L, text.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "text");
|
|
|
|
|
|
|
|
lua_pushstring(L, "comment");
|
|
|
|
|
|
|
|
} else if (raw[0] == _T('[')) {
|
|
|
|
lua_pushstring(L, "head");
|
|
|
|
|
|
|
|
} else if (section.Lower() == _T("[script info]")) {
|
|
|
|
// assumed "info" class
|
|
|
|
|
|
|
|
// first "key"
|
|
|
|
wxString key = raw.BeforeFirst(_T(':'));
|
|
|
|
lua_pushstring(L, key.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "key");
|
|
|
|
|
|
|
|
// then "value"
|
|
|
|
wxString value = raw.AfterFirst(_T(':'));
|
|
|
|
lua_pushstring(L, value.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "value");
|
|
|
|
|
|
|
|
lua_pushstring(L, "info");
|
|
|
|
|
|
|
|
} else if (raw.Left(7).Lower() == _T("format:")) {
|
|
|
|
|
|
|
|
// TODO: parse the format line; just use a tokenizer
|
|
|
|
|
|
|
|
lua_pushstring(L, "format");
|
|
|
|
|
|
|
|
} else if (e->GetType() == ENTRY_DIALOGUE) {
|
|
|
|
AssDialogue *dia = e->GetAsDialogue(e);
|
|
|
|
|
|
|
|
lua_pushboolean(L, (int)dia->Comment);
|
|
|
|
lua_setfield(L, -2, "comment");
|
|
|
|
|
|
|
|
lua_pushnumber(L, dia->Layer);
|
|
|
|
lua_setfield(L, -2, "layer");
|
|
|
|
|
|
|
|
lua_pushnumber(L, dia->Start.GetMS());
|
|
|
|
lua_setfield(L, -2, "start_time");
|
|
|
|
lua_pushnumber(L, dia->End.GetMS());
|
|
|
|
lua_setfield(L, -2, "end_time");
|
|
|
|
|
|
|
|
lua_pushstring(L, dia->Style.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "style");
|
|
|
|
lua_pushstring(L, dia->Actor.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "actor");
|
|
|
|
|
2007-01-05 19:27:15 +01:00
|
|
|
lua_pushnumber(L, dia->Margin[0]);
|
2006-12-28 22:18:35 +01:00
|
|
|
lua_setfield(L, -2, "margin_l");
|
2007-01-05 19:27:15 +01:00
|
|
|
lua_pushnumber(L, dia->Margin[1]);
|
2006-12-28 22:18:35 +01:00
|
|
|
lua_setfield(L, -2, "margin_r");
|
2007-01-05 19:27:15 +01:00
|
|
|
lua_pushnumber(L, dia->Margin[2]);
|
2006-12-28 22:18:35 +01:00
|
|
|
lua_setfield(L, -2, "margin_t");
|
2007-01-05 19:27:15 +01:00
|
|
|
lua_pushnumber(L, dia->Margin[3]);
|
2006-12-28 22:18:35 +01:00
|
|
|
lua_setfield(L, -2, "margin_b");
|
|
|
|
|
|
|
|
lua_pushstring(L, dia->Effect.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "effect");
|
|
|
|
|
|
|
|
lua_pushstring(L, ""); // tentative AS5 field
|
|
|
|
lua_setfield(L, -2, "userdata");
|
|
|
|
|
|
|
|
lua_pushstring(L, dia->Text.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "text");
|
|
|
|
|
|
|
|
lua_pushstring(L, "dialogue");
|
|
|
|
|
|
|
|
} else if (e->GetType() == ENTRY_STYLE) {
|
|
|
|
AssStyle *sty = e->GetAsStyle(e);
|
|
|
|
|
|
|
|
lua_pushstring(L, sty->name.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "name");
|
|
|
|
|
|
|
|
lua_pushstring(L, sty->font.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "fontname");
|
|
|
|
lua_pushnumber(L, sty->fontsize);
|
|
|
|
lua_setfield(L, -2, "fontsize");
|
|
|
|
|
|
|
|
lua_pushstring(L, sty->primary.GetASSFormatted(true).mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "color1");
|
|
|
|
lua_pushstring(L, sty->secondary.GetASSFormatted(true).mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "color2");
|
|
|
|
lua_pushstring(L, sty->outline.GetASSFormatted(true).mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "color3");
|
|
|
|
lua_pushstring(L, sty->shadow.GetASSFormatted(true).mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "color4");
|
|
|
|
|
|
|
|
lua_pushboolean(L, (int)sty->bold);
|
|
|
|
lua_setfield(L, -2, "bold");
|
|
|
|
lua_pushboolean(L, (int)sty->italic);
|
|
|
|
lua_setfield(L, -2, "italic");
|
|
|
|
lua_pushboolean(L, (int)sty->underline);
|
|
|
|
lua_setfield(L, -2, "underline");
|
|
|
|
lua_pushboolean(L, (int)sty->strikeout);
|
|
|
|
lua_setfield(L, -2, "strikeout");
|
|
|
|
|
|
|
|
lua_pushnumber(L, sty->scalex);
|
|
|
|
lua_setfield(L, -2, "scale_x");
|
|
|
|
lua_pushnumber(L, sty->scaley);
|
|
|
|
lua_setfield(L, -2, "scale_y");
|
|
|
|
|
|
|
|
lua_pushnumber(L, sty->spacing);
|
|
|
|
lua_setfield(L, -2, "spacing");
|
|
|
|
|
|
|
|
lua_pushnumber(L, sty->angle);
|
|
|
|
lua_setfield(L, -2, "angle");
|
|
|
|
|
|
|
|
lua_pushnumber(L, sty->borderstyle);
|
|
|
|
lua_setfield(L, -2, "borderstyle");
|
|
|
|
lua_pushnumber(L, sty->outline_w);
|
|
|
|
lua_setfield(L, -2, "outline");
|
|
|
|
lua_pushnumber(L, sty->shadow_w);
|
|
|
|
lua_setfield(L, -2, "shadow");
|
|
|
|
|
|
|
|
lua_pushnumber(L, sty->alignment);
|
|
|
|
lua_setfield(L, -2, "align");
|
|
|
|
|
|
|
|
lua_pushnumber(L, sty->MarginL);
|
|
|
|
lua_setfield(L, -2, "margin_l");
|
|
|
|
lua_pushnumber(L, sty->MarginR);
|
|
|
|
lua_setfield(L, -2, "margin_r");
|
|
|
|
lua_pushnumber(L, sty->MarginV); // duplicating MarginV to margin_t and margin_b here
|
|
|
|
lua_setfield(L, -2, "margin_t");
|
|
|
|
lua_pushnumber(L, sty->MarginV);
|
|
|
|
lua_setfield(L, -2, "margin_b");
|
|
|
|
|
|
|
|
lua_pushnumber(L, sty->encoding);
|
|
|
|
lua_setfield(L, -2, "encoding");
|
|
|
|
|
|
|
|
lua_pushnumber(L, 2); // From STS.h: "0: window, 1: video, 2: undefined (~window)"
|
|
|
|
lua_setfield(L, -2, "relative_to");
|
|
|
|
|
|
|
|
lua_pushboolean(L, false); // vertical writing, tentative AS5 field
|
|
|
|
lua_setfield(L, -2, "vertical");
|
|
|
|
|
|
|
|
lua_pushstring(L, "style");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
lua_pushstring(L, "unknown");
|
|
|
|
}
|
|
|
|
// store class of item; last thing done for each class specific code must be pushing the class name
|
|
|
|
lua_setfield(L, -2, "class");
|
|
|
|
}
|
|
|
|
|
|
|
|
AssEntry *LuaAssFile::LuaToAssEntry(lua_State *L)
|
|
|
|
{
|
|
|
|
// assume an assentry table is on the top of the stack
|
|
|
|
// convert it to a real AssEntry object, and pop the table from the stack
|
|
|
|
|
|
|
|
if (!lua_istable(L, -1)) {
|
|
|
|
lua_pushstring(L, "Can't convert a non-table value to AssEntry");
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_getfield(L, -1, "class");
|
|
|
|
if (!lua_isstring(L, -1)) {
|
|
|
|
lua_pushstring(L, "Table lacks 'class' field, can't convert to AssEntry");
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
wxString lclass(lua_tostring(L, -1), wxConvUTF8);
|
|
|
|
lclass.MakeLower();
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
AssEntry *result;
|
|
|
|
|
|
|
|
#define GETSTRING(varname, fieldname, lineclass) \
|
|
|
|
lua_getfield(L, -1, fieldname); \
|
|
|
|
if (!lua_isstring(L, -1)) { \
|
|
|
|
lua_pushstring(L, "Invalid string '" fieldname "' field in '" lineclass "' class subtitle line"); \
|
|
|
|
lua_error(L); \
|
|
|
|
return 0; \
|
|
|
|
} \
|
|
|
|
wxString varname (lua_tostring(L, -1), wxConvUTF8); \
|
|
|
|
lua_pop(L, 1);
|
|
|
|
#define GETFLOAT(varname, fieldname, lineclass) \
|
|
|
|
lua_getfield(L, -1, fieldname); \
|
|
|
|
if (!lua_isnumber(L, -1)) { \
|
|
|
|
lua_pushstring(L, "Invalid number '" fieldname "' field in '" lineclass "' class subtitle line"); \
|
|
|
|
lua_error(L); \
|
|
|
|
return 0; \
|
|
|
|
} \
|
|
|
|
float varname = lua_tonumber(L, -1); \
|
|
|
|
lua_pop(L, 1);
|
|
|
|
#define GETINT(varname, fieldname, lineclass) \
|
|
|
|
lua_getfield(L, -1, fieldname); \
|
|
|
|
if (!lua_isnumber(L, -1)) { \
|
|
|
|
lua_pushstring(L, "Invalid number '" fieldname "' field in '" lineclass "' class subtitle line"); \
|
|
|
|
lua_error(L); \
|
|
|
|
return 0; \
|
|
|
|
} \
|
|
|
|
int varname = lua_tointeger(L, -1); \
|
|
|
|
lua_pop(L, 1);
|
|
|
|
#define GETBOOL(varname, fieldname, lineclass) \
|
|
|
|
lua_getfield(L, -1, fieldname); \
|
|
|
|
if (!lua_isboolean(L, -1)) { \
|
|
|
|
lua_pushstring(L, "Invalid boolean '" fieldname "' field in '" lineclass "' class subtitle line"); \
|
|
|
|
lua_error(L); \
|
|
|
|
return 0; \
|
|
|
|
} \
|
|
|
|
bool varname = !!lua_toboolean(L, -1); \
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
GETSTRING(section, "section", "common")
|
|
|
|
|
|
|
|
if (lclass == _T("clear")) {
|
|
|
|
result = new AssEntry(_T(""));
|
|
|
|
result->group = section;
|
|
|
|
|
|
|
|
} else if (lclass == _T("comment")) {
|
|
|
|
GETSTRING(raw, "text", "comment")
|
|
|
|
raw.Prepend(_T(";"));
|
|
|
|
result = new AssEntry(raw);
|
|
|
|
result->group = section;
|
|
|
|
|
|
|
|
} else if (lclass == _T("head")) {
|
|
|
|
result = new AssEntry(section);
|
|
|
|
result->group = section;
|
|
|
|
|
|
|
|
} else if (lclass == _T("info")) {
|
|
|
|
GETSTRING(key, "key", "info")
|
|
|
|
GETSTRING(value, "value", "info")
|
|
|
|
result = new AssEntry(wxString::Format(_T("%s: %s"), key.c_str(), value.c_str()));
|
|
|
|
result->group = _T("[Script Info]"); // just so it can be read correctly back
|
|
|
|
|
|
|
|
} else if (lclass == _T("format")) {
|
|
|
|
// ohshi- ...
|
|
|
|
// *FIXME* maybe ignore the actual data and just put some default stuff based on section?
|
|
|
|
result = new AssEntry(_T("Format: Auto4,Is,Broken"));
|
|
|
|
result->group = section;
|
|
|
|
|
|
|
|
} else if (lclass == _T("style")) {
|
|
|
|
GETSTRING(name, "name", "style")
|
|
|
|
GETSTRING(fontname, "fontname", "style")
|
|
|
|
GETFLOAT(fontsize, "fontsize", "style")
|
|
|
|
GETSTRING(color1, "color1", "style")
|
|
|
|
GETSTRING(color2, "color2", "style")
|
|
|
|
GETSTRING(color3, "color3", "style")
|
|
|
|
GETSTRING(color4, "color4", "style")
|
|
|
|
GETBOOL(bold, "bold", "style")
|
|
|
|
GETBOOL(italic, "italic", "style")
|
|
|
|
GETBOOL(underline, "underline", "style")
|
|
|
|
GETBOOL(strikeout, "strikeout", "style")
|
|
|
|
GETFLOAT(scale_x, "scale_x", "style")
|
|
|
|
GETFLOAT(scale_y, "scale_y", "style")
|
|
|
|
GETINT(spacing, "spacing", "style")
|
|
|
|
GETFLOAT(angle, "angle", "style")
|
|
|
|
GETINT(borderstyle, "borderstyle", "style")
|
|
|
|
GETFLOAT(outline, "outline", "style")
|
|
|
|
GETFLOAT(shadow, "shadow", "style")
|
|
|
|
GETINT(align, "align", "style")
|
|
|
|
GETINT(margin_l, "margin_l", "style")
|
|
|
|
GETINT(margin_r, "margin_r", "style")
|
|
|
|
GETINT(margin_t, "margin_t", "style")
|
2007-01-05 19:27:15 +01:00
|
|
|
GETINT(margin_b, "margin_b", "style")
|
2006-12-28 22:18:35 +01:00
|
|
|
GETINT(encoding, "encoding", "style")
|
|
|
|
// leaving out relative_to and vertical
|
|
|
|
|
|
|
|
AssStyle *sty = new AssStyle();
|
|
|
|
sty->name = name;
|
|
|
|
sty->font = fontname;
|
|
|
|
sty->fontsize = fontsize;
|
|
|
|
sty->primary.ParseASS(color1);
|
|
|
|
sty->secondary.ParseASS(color2);
|
|
|
|
sty->outline.ParseASS(color3);
|
|
|
|
sty->shadow.ParseASS(color4);
|
|
|
|
sty->bold = bold;
|
|
|
|
sty->italic = italic;
|
|
|
|
sty->underline = underline;
|
|
|
|
sty->strikeout = strikeout;
|
|
|
|
sty->scalex = scale_x;
|
|
|
|
sty->scaley = scale_y;
|
|
|
|
sty->spacing = spacing;
|
|
|
|
sty->angle = angle;
|
|
|
|
sty->borderstyle = borderstyle;
|
|
|
|
sty->outline_w = outline;
|
|
|
|
sty->shadow_w = shadow;
|
|
|
|
sty->alignment = align;
|
|
|
|
sty->MarginL = margin_l;
|
|
|
|
sty->MarginR = margin_r;
|
|
|
|
sty->MarginV = margin_t;
|
|
|
|
sty->encoding = encoding;
|
|
|
|
sty->UpdateData();
|
|
|
|
|
|
|
|
result = sty;
|
|
|
|
|
|
|
|
} else if (lclass == _T("styleex")) {
|
|
|
|
lua_pushstring(L, "Found line with class 'styleex' which is not supported. Wait until AS5 is a reality.");
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else if (lclass == _T("dialogue")) {
|
|
|
|
GETBOOL(comment, "comment", "dialogue")
|
|
|
|
GETINT(layer, "layer", "dialogue")
|
|
|
|
GETINT(start_time, "start_time", "dialogue")
|
|
|
|
GETINT(end_time, "end_time", "dialogue")
|
|
|
|
GETSTRING(style, "style", "dialogue")
|
|
|
|
GETSTRING(actor, "actor", "dialogue")
|
|
|
|
GETINT(margin_l, "margin_l", "dialogue")
|
|
|
|
GETINT(margin_r, "margin_r", "dialogue")
|
|
|
|
GETINT(margin_t, "margin_t", "dialogue")
|
2007-01-05 19:27:15 +01:00
|
|
|
GETINT(margin_b, "margin_b", "dialogue")
|
2006-12-28 22:18:35 +01:00
|
|
|
GETSTRING(effect, "effect", "dialogue")
|
|
|
|
//GETSTRING(userdata, "userdata", "dialogue")
|
|
|
|
GETSTRING(text, "text", "dialogue")
|
|
|
|
|
|
|
|
AssDialogue *dia = new AssDialogue();
|
|
|
|
dia->Comment = comment;
|
|
|
|
dia->Layer = layer;
|
|
|
|
dia->Start.SetMS(start_time);
|
|
|
|
dia->End.SetMS(end_time);
|
|
|
|
dia->Style = style;
|
|
|
|
dia->Actor = actor;
|
2007-01-05 19:27:15 +01:00
|
|
|
dia->Margin[0] = margin_l;
|
|
|
|
dia->Margin[1] = margin_r;
|
|
|
|
dia->Margin[2] = margin_t;
|
|
|
|
dia->Margin[3] = margin_b;
|
2006-12-28 22:18:35 +01:00
|
|
|
dia->Effect = effect;
|
|
|
|
dia->Text = text;
|
|
|
|
dia->UpdateData();
|
|
|
|
|
|
|
|
result = dia;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
lua_pushfstring(L, "Found line with unknown class: %s", lclass.mb_str(wxConvUTF8));
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef GETSTRING
|
|
|
|
#undef GETFLOAT
|
|
|
|
#undef GETINT
|
|
|
|
#undef GETBOOL
|
|
|
|
|
|
|
|
//lua_pop(L, 1); // the function shouldn't eat the table it converted
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LuaAssFile::GetAssEntry(int n)
|
|
|
|
{
|
|
|
|
entryIter e;
|
|
|
|
if (n < last_entry_id/2) {
|
|
|
|
// fastest to search from start
|
|
|
|
e = ass->Line.begin();
|
|
|
|
last_entry_id = n;
|
|
|
|
while (n-- > 0) e++;
|
|
|
|
last_entry_ptr = e;
|
|
|
|
|
|
|
|
} else if (last_entry_id + n > last_entry_id + ((int)ass->Line.size() - last_entry_id)/2) {
|
|
|
|
// fastest to search from end
|
|
|
|
int i = (int)ass->Line.size();
|
|
|
|
e = ass->Line.end();
|
|
|
|
last_entry_id = n;
|
|
|
|
while (i-- > n) e--;
|
|
|
|
last_entry_ptr = e;
|
|
|
|
|
|
|
|
} else if (last_entry_id > n) {
|
|
|
|
// search backwards from last_entry_id
|
|
|
|
e = last_entry_ptr;
|
|
|
|
while (n < last_entry_id) e--, last_entry_id--;
|
|
|
|
last_entry_ptr = e;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// search forwards from last_entry_id
|
|
|
|
e = last_entry_ptr;
|
|
|
|
// reqid and last_entry_id might be equal here, make sure the loop will still work
|
|
|
|
while (n > last_entry_id) e++, last_entry_id++;
|
|
|
|
last_entry_ptr = e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::ObjectIndexRead(lua_State *L)
|
|
|
|
{
|
|
|
|
LuaAssFile *laf = GetObjPointer(L, 1);
|
|
|
|
|
|
|
|
switch (lua_type(L, 2)) {
|
|
|
|
|
|
|
|
case LUA_TNUMBER:
|
|
|
|
{
|
|
|
|
// read an indexed AssEntry
|
|
|
|
|
|
|
|
// get requested index
|
|
|
|
int reqid = lua_tointeger(L, 2);
|
|
|
|
if (reqid <= 0 || reqid > (int)laf->ass->Line.size()) {
|
|
|
|
lua_pushfstring(L, "Requested out-of-range line from subtitle file: %d", reqid);
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
laf->GetAssEntry(reqid-1);
|
|
|
|
laf->AssEntryToLua(L, *laf->last_entry_ptr);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
case LUA_TSTRING:
|
|
|
|
{
|
|
|
|
// either return n or a function doing further stuff
|
|
|
|
const char *idx = lua_tostring(L, 2);
|
|
|
|
|
|
|
|
if (strcmp(idx, "n") == 0) {
|
|
|
|
// get number of items
|
|
|
|
lua_pushnumber(L, laf->ass->Line.size());
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
} else if (strcmp(idx, "delete") == 0) {
|
|
|
|
// make a "delete" function
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
lua_pushcclosure(L, ObjectDelete, 1);
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
} else if (strcmp(idx, "deleterange") == 0) {
|
|
|
|
// make a "deleterange" function
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
lua_pushcclosure(L, ObjectDeleteRange, 1);
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
} else if (strcmp(idx, "insert") == 0) {
|
|
|
|
// make an "insert" function
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
lua_pushcclosure(L, ObjectInsert, 1);
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
} else if (strcmp(idx, "append") == 0) {
|
|
|
|
// make an "append" function
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
lua_pushcclosure(L, ObjectAppend, 1);
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// idiot
|
|
|
|
lua_pushfstring(L, "Invalid indexing in Subtitle File object: '%s'", idx);
|
|
|
|
lua_error(L);
|
|
|
|
// should never return
|
|
|
|
}
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
// crap, user is stupid!
|
|
|
|
lua_pushfstring(L, "Attempt to index a Subtitle File object with value of type '%s'.", lua_typename(L, lua_type(L, 2)));
|
|
|
|
lua_error(L);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(false);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::ObjectIndexWrite(lua_State *L)
|
|
|
|
{
|
|
|
|
// instead of implementing everything twice, just call the other modification-functions from here
|
|
|
|
// after modifying the stack to match their expectations
|
|
|
|
|
|
|
|
if (!lua_isnumber(L, 2)) {
|
|
|
|
lua_pushstring(L, "Attempt to write to non-numeric index in subtitle index");
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
LuaAssFile *laf = GetObjPointer(L, 1);
|
|
|
|
laf->CheckAllowModify();
|
|
|
|
|
|
|
|
int n = lua_tointeger(L, 2);
|
|
|
|
|
|
|
|
if (n < 0) {
|
|
|
|
// insert line so new index is n
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
lua_pushcclosure(L, ObjectInsert, 1);
|
|
|
|
lua_pushinteger(L, -n);
|
|
|
|
lua_pushvalue(L, 3);
|
|
|
|
lua_call(L, 2, 0);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else if (n == 0) {
|
|
|
|
// append line to list
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
lua_pushcclosure(L, ObjectAppend, 1);
|
|
|
|
lua_pushvalue(L, 3);
|
|
|
|
lua_call(L, 1, 0);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// replace line at index n or delete
|
|
|
|
if (!lua_isnil(L, 3)) {
|
|
|
|
// insert
|
|
|
|
AssEntry *e = LuaToAssEntry(L);
|
|
|
|
laf->GetAssEntry(n-1);
|
|
|
|
delete *laf->last_entry_ptr;
|
|
|
|
*laf->last_entry_ptr = e;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// delete
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
lua_pushcclosure(L, ObjectDelete, 1);
|
|
|
|
lua_pushvalue(L, 2);
|
|
|
|
lua_call(L, 1, 0);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::ObjectGetLen(lua_State *L)
|
|
|
|
{
|
|
|
|
LuaAssFile *laf = GetObjPointer(L, 1);
|
|
|
|
lua_pushnumber(L, laf->ass->Line.size());
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::ObjectDelete(lua_State *L)
|
|
|
|
{
|
|
|
|
LuaAssFile *laf = GetObjPointer(L, lua_upvalueindex(1));
|
|
|
|
|
|
|
|
laf->CheckAllowModify();
|
|
|
|
|
|
|
|
// get number of items to delete
|
|
|
|
int itemcount = lua_gettop(L);
|
|
|
|
std::vector<int> ids;
|
|
|
|
ids.reserve(itemcount);
|
|
|
|
|
|
|
|
// sort the item id's so we can delete from last to first to preserve original numbering
|
|
|
|
while (itemcount > 0) {
|
|
|
|
if (!lua_isnumber(L, itemcount)) {
|
|
|
|
lua_pushstring(L, "Attempt to delete non-numeric line id from Subtitle Object");
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
int n = lua_tointeger(L, itemcount);
|
|
|
|
if (n > (int)laf->ass->Line.size() || n < 1) {
|
|
|
|
lua_pushstring(L, "Attempt to delete out of range line id from Subtitle Object");
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
ids.push_back(n-1); // make C-style line ids
|
|
|
|
--itemcount;
|
|
|
|
}
|
|
|
|
std::sort(ids.begin(), ids.end());
|
|
|
|
|
|
|
|
// now delete the id's backwards
|
|
|
|
// start with the last one, to initialise things
|
|
|
|
laf->GetAssEntry(ids.back());
|
|
|
|
// get an iterator to it, and increase last_entry_ptr so it'll still be valid after deletion, and point to the right index
|
|
|
|
entryIter e = laf->last_entry_ptr++;
|
|
|
|
laf->ass->Line.erase(e);
|
|
|
|
int n = laf->last_entry_id;
|
|
|
|
for (int i = (int)ids.size()-2; i >= 0; --i) {
|
|
|
|
int id = ids[i];
|
|
|
|
while (id > n--) laf->last_entry_ptr--;
|
|
|
|
e = laf->last_entry_ptr++;
|
|
|
|
delete *e;
|
|
|
|
laf->ass->Line.erase(e);
|
|
|
|
}
|
|
|
|
laf->last_entry_id = n;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::ObjectDeleteRange(lua_State *L)
|
|
|
|
{
|
|
|
|
LuaAssFile *laf = GetObjPointer(L, lua_upvalueindex(1));
|
|
|
|
|
|
|
|
laf->CheckAllowModify();
|
|
|
|
|
|
|
|
if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2)) {
|
|
|
|
lua_pushstring(L, "Non-numeric argument given to deleterange");
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int a = lua_tointeger(L, 1), b = lua_tointeger(L, 2);
|
|
|
|
|
|
|
|
if (a < 1) a = 1;
|
|
|
|
if (b > (int)laf->ass->Line.size()) b = (int)laf->ass->Line.size();
|
|
|
|
|
|
|
|
if (b < a) return 0;
|
|
|
|
|
|
|
|
if (a == b) {
|
|
|
|
laf->GetAssEntry(a-1);
|
|
|
|
entryIter e = laf->last_entry_ptr++;
|
|
|
|
delete *e;
|
|
|
|
laf->ass->Line.erase(e);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
entryIter ai, bi;
|
|
|
|
laf->GetAssEntry(a-1);
|
|
|
|
ai = laf->last_entry_ptr;
|
|
|
|
laf->GetAssEntry(b-1);
|
|
|
|
bi = laf->last_entry_ptr;
|
|
|
|
laf->last_entry_ptr++;
|
|
|
|
|
|
|
|
laf->ass->Line.erase(ai, bi);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::ObjectAppend(lua_State *L)
|
|
|
|
{
|
|
|
|
LuaAssFile *laf = GetObjPointer(L, lua_upvalueindex(1));
|
|
|
|
|
|
|
|
laf->CheckAllowModify();
|
|
|
|
|
|
|
|
int n = lua_gettop(L);
|
|
|
|
|
|
|
|
if (laf->last_entry_ptr != laf->ass->Line.begin()) {
|
|
|
|
laf->last_entry_ptr--;
|
|
|
|
laf->last_entry_id--;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 1; i <= n; i++) {
|
|
|
|
lua_pushvalue(L, i);
|
|
|
|
AssEntry *e = LuaToAssEntry(L);
|
|
|
|
laf->ass->Line.push_back(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::ObjectInsert(lua_State *L)
|
|
|
|
{
|
|
|
|
LuaAssFile *laf = GetObjPointer(L, lua_upvalueindex(1));
|
|
|
|
|
|
|
|
laf->CheckAllowModify();
|
|
|
|
|
|
|
|
if (!lua_isnumber(L, 1)) {
|
|
|
|
lua_pushstring(L, "Can't insert at non-numeric index");
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int n = lua_gettop(L);
|
|
|
|
|
|
|
|
laf->GetAssEntry(lua_tonumber(L, 1)-1);
|
|
|
|
|
|
|
|
for (int i = 2; i <= n; i++) {
|
|
|
|
lua_pushvalue(L, i);
|
|
|
|
AssEntry *e = LuaToAssEntry(L);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
laf->ass->Line.insert(laf->last_entry_ptr, e);
|
|
|
|
laf->last_entry_id++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::ObjectGarbageCollect(lua_State *L)
|
|
|
|
{
|
|
|
|
LuaAssFile *laf = GetObjPointer(L, 1);
|
|
|
|
delete laf;
|
|
|
|
wxLogDebug(_T(">>gc<< Garbage collected LuaAssFile"));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::LuaParseTagData(lua_State *L)
|
|
|
|
{
|
|
|
|
lua_newtable(L);
|
|
|
|
// TODO
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::LuaUnparseTagData(lua_State *L)
|
|
|
|
{
|
|
|
|
lua_pushstring(L, "");
|
|
|
|
// TODO
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::LuaParseKaraokeData(lua_State *L)
|
|
|
|
{
|
|
|
|
AssEntry *e = LuaToAssEntry(L);
|
|
|
|
if (e->GetType() != ENTRY_DIALOGUE) {
|
|
|
|
delete e;
|
|
|
|
lua_pushstring(L, "Attempt to create karaoke table from non-dialogue subtitle line");
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
AssDialogue *dia = e->GetAsDialogue(e);
|
|
|
|
dia->ParseASSTags();
|
|
|
|
|
|
|
|
int kcount = 0;
|
|
|
|
int kdur = 0;
|
|
|
|
int ktime = 0;
|
|
|
|
wxString ktag = _T("");
|
|
|
|
wxString ktext = _T("");
|
|
|
|
wxString ktext_stripped = _T("");
|
|
|
|
|
|
|
|
lua_newtable(L);
|
|
|
|
|
|
|
|
for (int i = 0; i < (int)dia->Blocks.size(); i++) {
|
|
|
|
AssDialogueBlock *block = dia->Blocks[i];
|
|
|
|
|
|
|
|
switch (block->type) {
|
|
|
|
|
|
|
|
case BLOCK_BASE:
|
|
|
|
assert(block->type != BLOCK_BASE);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BLOCK_PLAIN:
|
|
|
|
ktext += block->text;
|
|
|
|
ktext_stripped += block->text;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BLOCK_DRAWING:
|
|
|
|
// a drawing is regarded as a kind of control code here, so it's just stripped away
|
|
|
|
ktext += block->text;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BLOCK_OVERRIDE: {
|
|
|
|
bool brackets_open = false;
|
|
|
|
AssDialogueBlockOverride *ovr = block->GetAsOverride(block);
|
|
|
|
|
|
|
|
for (int j = 0; j < (int)ovr->Tags.size(); j++) {
|
|
|
|
AssOverrideTag *tag = ovr->Tags[j];
|
|
|
|
|
|
|
|
if (tag->IsValid() && tag->Name.Mid(0,2).CmpNoCase(_T("\\k")) == 0) {
|
|
|
|
// karaoke tag
|
|
|
|
if (brackets_open) {
|
|
|
|
ktext += _T("}");
|
|
|
|
brackets_open = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// store to lua
|
|
|
|
lua_newtable(L);
|
|
|
|
lua_pushnumber(L, kdur);
|
|
|
|
lua_setfield(L, -2, "duration");
|
|
|
|
lua_pushnumber(L, ktime);
|
|
|
|
lua_setfield(L, -2, "start_time");
|
|
|
|
lua_pushnumber(L, ktime+kdur);
|
|
|
|
lua_setfield(L, -2, "end_time");
|
|
|
|
lua_pushstring(L, ktag.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "tag");
|
|
|
|
lua_pushstring(L, ktext.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "text");
|
|
|
|
lua_pushstring(L, ktext_stripped.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "text_stripped");
|
|
|
|
lua_rawseti(L, -2, kcount);
|
|
|
|
|
|
|
|
// prepare new syllable
|
|
|
|
kcount++;
|
|
|
|
ktag = tag->Name.Mid(1);
|
|
|
|
// check if it's a "set time" tag, special handling for that (depends on previous syllable duration)
|
|
|
|
if (ktag == _T("kt")) {
|
|
|
|
ktime = tag->Params[0]->AsInt() * 10;
|
|
|
|
kdur = 0;
|
|
|
|
} else {
|
|
|
|
ktime += kdur; // duration of previous syllable
|
|
|
|
kdur = tag->Params[0]->AsInt() * 10;
|
|
|
|
}
|
|
|
|
ktext.clear();
|
|
|
|
ktext_stripped.clear();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// not karaoke tag
|
|
|
|
if (!brackets_open) {
|
|
|
|
ktext += _T("{");
|
|
|
|
brackets_open = true;
|
|
|
|
}
|
|
|
|
ktext += tag->ToString();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (brackets_open) {
|
|
|
|
ktext += _T("}");
|
|
|
|
brackets_open = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
dia->ClearBlocks();
|
|
|
|
|
|
|
|
// store final syllable/block to lua
|
|
|
|
lua_newtable(L);
|
|
|
|
lua_pushnumber(L, kdur);
|
|
|
|
lua_setfield(L, -2, "duration");
|
|
|
|
lua_pushnumber(L, ktime);
|
|
|
|
lua_setfield(L, -2, "start_time");
|
|
|
|
lua_pushnumber(L, ktime+kdur);
|
|
|
|
lua_setfield(L, -2, "end_time");
|
|
|
|
lua_pushstring(L, ktag.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "tag");
|
|
|
|
lua_pushstring(L, ktext.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "text");
|
|
|
|
lua_pushstring(L, ktext_stripped.mb_str(wxConvUTF8));
|
|
|
|
lua_setfield(L, -2, "text_stripped");
|
|
|
|
lua_rawseti(L, -2, kcount);
|
|
|
|
|
|
|
|
delete dia;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LuaAssFile::LuaSetUndoPoint(lua_State *L)
|
|
|
|
{
|
|
|
|
LuaAssFile *laf = GetObjPointer(L, lua_upvalueindex(1));
|
|
|
|
if (!laf->can_set_undo) {
|
|
|
|
lua_pushstring(L, "Attempt to set an undo point in a context without undo-support.");
|
|
|
|
lua_error(L);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxString description;
|
|
|
|
if (lua_isstring(L, 1)) {
|
|
|
|
description = wxString(lua_tostring(L, 1), wxConvUTF8);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
2006-12-28 23:31:33 +01:00
|
|
|
AssFile::top->FlagAsModified(); // TODO: make undo system support description of action undone
|
2006-12-28 22:18:35 +01:00
|
|
|
|
|
|
|
laf->ass = AssFile::top; // make sure we're still working on the most recent undo point
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
LuaAssFile *LuaAssFile::GetObjPointer(lua_State *L, int idx)
|
|
|
|
{
|
|
|
|
assert(lua_type(L, idx) == LUA_TUSERDATA);
|
|
|
|
void *ud = lua_touserdata(L, idx);
|
|
|
|
return *((LuaAssFile**)ud);
|
|
|
|
}
|
|
|
|
|
|
|
|
LuaAssFile::~LuaAssFile()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
LuaAssFile::LuaAssFile(lua_State *_L, AssFile *_ass, bool _can_modify, bool _can_set_undo)
|
|
|
|
: ass(_ass)
|
|
|
|
, L(_L)
|
|
|
|
, can_modify(_can_modify)
|
|
|
|
, can_set_undo(_can_set_undo)
|
|
|
|
{
|
|
|
|
// prepare cursor
|
|
|
|
last_entry_ptr = ass->Line.begin();
|
|
|
|
last_entry_id = 0;
|
|
|
|
|
|
|
|
// prepare userdata object
|
|
|
|
void *ud = lua_newuserdata(L, sizeof(LuaAssFile*));
|
|
|
|
*((LuaAssFile**)ud) = this;
|
|
|
|
|
|
|
|
// make the metatable
|
|
|
|
lua_newtable(L);
|
|
|
|
lua_pushcfunction(L, ObjectIndexRead);
|
|
|
|
lua_setfield(L, -2, "__index");
|
|
|
|
lua_pushcfunction(L, ObjectIndexWrite);
|
|
|
|
lua_setfield(L, -2, "__newindex");
|
|
|
|
lua_pushcfunction(L, ObjectGetLen);
|
|
|
|
lua_setfield(L, -2, "__len");
|
|
|
|
lua_pushcfunction(L, ObjectGarbageCollect);
|
|
|
|
lua_setfield(L, -2, "__gc");
|
|
|
|
lua_setmetatable(L, -2);
|
|
|
|
|
|
|
|
// register misc functions
|
|
|
|
// assume the "aegisub" global table exists
|
|
|
|
lua_getglobal(L, "aegisub");
|
|
|
|
assert(lua_type(L, -2) == LUA_TUSERDATA);
|
|
|
|
lua_pushvalue(L, -2); // the userdata object
|
|
|
|
lua_pushcclosure(L, LuaParseTagData, 1);
|
|
|
|
lua_setfield(L, -2, "parse_tag_data");
|
|
|
|
assert(lua_type(L, -2) == LUA_TUSERDATA);
|
|
|
|
lua_pushvalue(L, -2);
|
|
|
|
lua_pushcclosure(L, LuaUnparseTagData, 1);
|
|
|
|
lua_setfield(L, -2, "unparse_tag_data");
|
|
|
|
assert(lua_type(L, -2) == LUA_TUSERDATA);
|
|
|
|
lua_pushvalue(L, -2);
|
|
|
|
lua_pushcclosure(L, LuaParseKaraokeData, 1);
|
|
|
|
lua_setfield(L, -2, "parse_karaoke_data");
|
|
|
|
assert(lua_type(L, -2) == LUA_TUSERDATA);
|
|
|
|
lua_pushvalue(L, -2);
|
|
|
|
lua_pushcclosure(L, LuaSetUndoPoint, 1);
|
|
|
|
lua_setfield(L, -2, "set_undo_point");
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|