mirror of https://github.com/odrling/Aegisub
1454 lines
41 KiB
C++
1454 lines
41 KiB
C++
// Copyright (c) 2005, 2006, 2007, 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_auto3.h"
|
|
#include "auto4_lua.h"
|
|
#include "../lua51/src/lualib.h"
|
|
#include "../lua51/src/lauxlib.h"
|
|
#include "options.h"
|
|
#include "string_codec.h"
|
|
#include "vfr.h"
|
|
#include "ass_override.h"
|
|
|
|
namespace Automation4 {
|
|
|
|
// Helper functions for reading/writing data
|
|
|
|
static inline void L_settable(lua_State *L, int table, const char *key, lua_Number val)
|
|
{
|
|
lua_pushstring(L, key);
|
|
lua_pushnumber(L, val);
|
|
if (table > 0 || table < -100) {
|
|
lua_settable(L, table);
|
|
} else {
|
|
lua_settable(L, table-2);
|
|
}
|
|
}
|
|
|
|
static inline void L_settable(lua_State *L, int table, const char *key, wxString val)
|
|
{
|
|
//wxLogMessage(_T("Adding string at index '%s': %s"), wxString(key, wxConvUTF8), val);
|
|
lua_pushstring(L, key);
|
|
lua_pushstring(L, val.mb_str(wxConvUTF8));
|
|
if (table > 0 || table < -100) {
|
|
lua_settable(L, table);
|
|
} else {
|
|
lua_settable(L, table-2);
|
|
}
|
|
}
|
|
|
|
static inline void L_settable_bool(lua_State *L, int table, const char *key, bool val)
|
|
{
|
|
lua_pushstring(L, key);
|
|
lua_pushboolean(L, val?1:0);
|
|
if (table > 0 || table < -100) {
|
|
lua_settable(L, table);
|
|
} else {
|
|
lua_settable(L, table-2);
|
|
}
|
|
}
|
|
|
|
static inline void L_settable(lua_State *L, int table, wxString &key, lua_Number val)
|
|
{
|
|
L_settable(L, table, key.mb_str(wxConvUTF8), val);
|
|
}
|
|
|
|
static inline void L_settable(lua_State *L, int table, wxString &key, wxString val)
|
|
{
|
|
L_settable(L, table, key.mb_str(wxConvUTF8), val);
|
|
}
|
|
|
|
static inline void L_settable_bool(lua_State *L, int table, wxString &key, bool val)
|
|
{
|
|
L_settable_bool(L, table, key.mb_str(wxConvUTF8).data(), val);
|
|
}
|
|
|
|
static inline void L_settable_kara(lua_State *L, int table, int index, int duration, wxString &kind, wxString &text, wxString &text_stripped)
|
|
{
|
|
lua_newtable(L);
|
|
L_settable(L, -1, "duration", duration);
|
|
L_settable(L, -1, "kind", kind);
|
|
L_settable(L, -1, "text", text);
|
|
L_settable(L, -1, "text_stripped", text_stripped);
|
|
if (table > 0 || table < -100) {
|
|
lua_rawseti(L, table, index);
|
|
} else {
|
|
lua_rawseti(L, table-1, index);
|
|
}
|
|
}
|
|
|
|
static inline lua_Number L_gettableN(lua_State *L, const char *key)
|
|
{
|
|
lua_pushstring(L, key);
|
|
lua_gettable(L, -2);
|
|
lua_Number res = lua_tonumber(L, -1);
|
|
lua_settop(L, -2);
|
|
return res;
|
|
}
|
|
|
|
static inline wxString L_gettableS(lua_State *L, const char *key)
|
|
{
|
|
lua_pushstring(L, key);
|
|
lua_gettable(L, -2);
|
|
wxString res(lua_tostring(L, -1), wxConvUTF8);
|
|
lua_settop(L, -2);
|
|
return res;
|
|
}
|
|
|
|
static inline bool L_gettableB(lua_State *L, const char *key)
|
|
{
|
|
lua_pushstring(L, key);
|
|
lua_gettable(L, -2);
|
|
bool res = lua_toboolean(L, -1) != 0;
|
|
lua_settop(L, -2);
|
|
return res;
|
|
}
|
|
|
|
|
|
// Auto3ProgressSink
|
|
|
|
int Auto3ProgressSink::LuaSetStatus(lua_State *L)
|
|
{
|
|
Auto3ProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
|
|
wxString task(lua_tostring(L, 1), wxConvUTF8);
|
|
ps->SetTask(task);
|
|
return 0;
|
|
}
|
|
|
|
int Auto3ProgressSink::LuaOutputDebug(lua_State *L)
|
|
{
|
|
Auto3ProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
|
|
wxString msg(lua_tostring(L, 1), wxConvUTF8);
|
|
ps->AddDebugOutput(msg);
|
|
ps->AddDebugOutput(_T("\n"));
|
|
return 0;
|
|
}
|
|
|
|
int Auto3ProgressSink::LuaReportProgress(lua_State *L)
|
|
{
|
|
Auto3ProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
|
|
float progress = lua_tonumber(L, 1);
|
|
ps->SetProgress(progress);
|
|
return 0;
|
|
}
|
|
|
|
Auto3ProgressSink::Auto3ProgressSink(lua_State *_L, wxWindow *parent)
|
|
: ProgressSink(parent)
|
|
, L(_L)
|
|
{
|
|
Auto3ProgressSink **ud = (Auto3ProgressSink**)lua_newuserdata(L, sizeof(Auto3ProgressSink*));
|
|
*ud = this;
|
|
|
|
// register progress reporting stuff
|
|
lua_getglobal(L, "aegisub");
|
|
|
|
lua_pushvalue(L, -2);
|
|
lua_pushcclosure(L, LuaReportProgress, 1);
|
|
lua_setfield(L, -2, "report_progress");
|
|
|
|
lua_pushvalue(L, -2);
|
|
lua_pushcclosure(L, LuaOutputDebug, 1);
|
|
lua_setfield(L, -2, "output_debug");
|
|
|
|
lua_pushvalue(L, -2);
|
|
lua_pushcclosure(L, LuaSetStatus, 1);
|
|
lua_setfield(L, -2, "set_status");
|
|
|
|
// reference so other objects can also find the progress sink
|
|
lua_pushvalue(L, -2);
|
|
lua_setfield(L, LUA_REGISTRYINDEX, "progress_sink");
|
|
|
|
// Remove aegisub table and userdata object from stack
|
|
lua_pop(L, 2);
|
|
}
|
|
|
|
Auto3ProgressSink::~Auto3ProgressSink()
|
|
{
|
|
// remove progress reporting stuff
|
|
lua_getglobal(L, "aegisub");
|
|
lua_pushnil(L);
|
|
lua_setfield(L, -2, "report_progress");
|
|
lua_pushnil(L);
|
|
lua_setfield(L, -2, "output_debug");
|
|
lua_pushnil(L);
|
|
lua_setfield(L, -2, "set_status");
|
|
lua_pop(L, 1);
|
|
lua_pushnil(L);
|
|
lua_setfield(L, LUA_REGISTRYINDEX, "progress_sink");
|
|
}
|
|
|
|
Auto3ProgressSink* Auto3ProgressSink::GetObjPointer(lua_State *L, int idx)
|
|
{
|
|
assert(lua_type(L, idx) == LUA_TUSERDATA);
|
|
void *ud = lua_touserdata(L, idx);
|
|
return *((Auto3ProgressSink**)ud);
|
|
}
|
|
|
|
|
|
// Auto3ConfigDialog
|
|
|
|
wxWindow* Auto3ConfigDialog::CreateWindow(wxWindow *parent)
|
|
{
|
|
if (options.size() == 0)
|
|
return 0;
|
|
|
|
wxPanel *res = new wxPanel(parent, -1);
|
|
|
|
wxFlexGridSizer *sizer = new wxFlexGridSizer(2, 5, 5);
|
|
|
|
for (std::vector<Auto3ScriptConfigurationOption>::iterator opt = options.begin(); opt != options.end(); opt++) {
|
|
if (opt->kind == COK_INVALID)
|
|
continue;
|
|
|
|
Control control;
|
|
control.option = &*opt;
|
|
|
|
switch (opt->kind) {
|
|
case COK_LABEL:
|
|
control.control = new wxStaticText(res, -1, opt->label);
|
|
break;
|
|
|
|
case COK_TEXT:
|
|
control.control = new wxTextCtrl(res, -1, opt->value.stringval);
|
|
break;
|
|
|
|
case COK_INT:
|
|
control.control = new wxSpinCtrl(res, -1);
|
|
if (opt->min.isset && opt->max.isset) {
|
|
((wxSpinCtrl*)control.control)->SetRange(opt->min.intval, opt->max.intval);
|
|
} else if (opt->min.isset) {
|
|
((wxSpinCtrl*)control.control)->SetRange(opt->min.intval, 0x7fff);
|
|
} else if (opt->max.isset) {
|
|
((wxSpinCtrl*)control.control)->SetRange(-0x7fff, opt->max.intval);
|
|
} else {
|
|
((wxSpinCtrl*)control.control)->SetRange(-0x7fff, 0x7fff);
|
|
}
|
|
((wxSpinCtrl*)control.control)->SetValue(opt->value.intval);
|
|
break;
|
|
|
|
case COK_FLOAT:
|
|
control.control = new wxTextCtrl(res, -1, wxString::Format(_T("%f"), opt->value.floatval));
|
|
break;
|
|
|
|
case COK_BOOL:
|
|
control.control = new wxCheckBox(res, -1, opt->label);
|
|
((wxCheckBox*)control.control)->SetValue(opt->value.boolval);
|
|
break;
|
|
|
|
case COK_COLOUR:
|
|
// *FIXME* what to do here?
|
|
// just put a stupid edit box for now
|
|
control.control = new wxTextCtrl(res, -1, opt->value.colourval.GetASSFormatted(false));
|
|
break;
|
|
|
|
case COK_STYLE:
|
|
control.control = new wxChoice(res, -1, wxDefaultPosition, wxDefaultSize, AssFile::top->GetStyles());
|
|
((wxChoice*)control.control)->Insert(_T(""), 0);
|
|
break;
|
|
|
|
}
|
|
|
|
if (opt->kind != COK_LABEL && opt->kind != COK_BOOL) {
|
|
control.label = new wxStaticText(res, -1, opt->label);
|
|
sizer->Add(control.label, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL);
|
|
} else {
|
|
control.label = 0;
|
|
sizer->AddSpacer(0);
|
|
}
|
|
control.control->SetToolTip(opt->hint);
|
|
sizer->Add(control.control, 1, wxEXPAND);
|
|
|
|
controls.push_back(control);
|
|
}
|
|
|
|
res->SetSizerAndFit(sizer);
|
|
|
|
return res;
|
|
}
|
|
|
|
Auto3ConfigDialog::Auto3ConfigDialog(lua_State *L, const wxString &_ident)
|
|
: ident(_ident)
|
|
{
|
|
present = false;
|
|
if (!lua_istable(L, -1)) {
|
|
return;
|
|
}
|
|
|
|
int i = 1;
|
|
while (true) {
|
|
// get an element from the array
|
|
lua_pushnumber(L, i);
|
|
lua_gettable(L, -2);
|
|
|
|
// check if it was a table
|
|
if (!lua_istable(L, -1)) {
|
|
lua_pop(L, 1);
|
|
break;
|
|
}
|
|
|
|
// add a new config option and fill it
|
|
{
|
|
Auto3ScriptConfigurationOption opt;
|
|
options.push_back(opt);
|
|
}
|
|
Auto3ScriptConfigurationOption &opt = options.back();
|
|
|
|
// get the "kind"
|
|
lua_pushstring(L, "kind");
|
|
lua_gettable(L, -2);
|
|
if (lua_isstring(L, -1)) {
|
|
// use C standard lib functions here, as it's probably faster than messing around with unicode
|
|
// lua is known to always properly null-terminate strings, and the strings are known to be pure ascii
|
|
const char *kind = lua_tostring(L, -1);
|
|
if (strcmp(kind, "label") == 0) {
|
|
opt.kind = COK_LABEL;
|
|
} else if (strcmp(kind, "text") == 0) {
|
|
opt.kind = COK_TEXT;
|
|
} else if (strcmp(kind, "int") == 0) {
|
|
opt.kind = COK_INT;
|
|
} else if (strcmp(kind, "float") == 0) {
|
|
opt.kind = COK_FLOAT;
|
|
} else if (strcmp(kind, "bool") == 0) {
|
|
opt.kind = COK_BOOL;
|
|
} else if (strcmp(kind, "colour") == 0) {
|
|
opt.kind = COK_COLOUR;
|
|
} else if (strcmp(kind, "style") == 0) {
|
|
opt.kind = COK_STYLE;
|
|
} else {
|
|
opt.kind = COK_INVALID;
|
|
}
|
|
} else {
|
|
opt.kind = COK_INVALID;
|
|
}
|
|
|
|
// remove "kind" string from stack again
|
|
lua_pop(L, 1);
|
|
|
|
// no need to check for rest if this one is already deemed invalid
|
|
if (opt.kind != COK_INVALID) {
|
|
// name
|
|
lua_pushstring(L, "name");
|
|
lua_gettable(L, -2);
|
|
if (lua_isstring(L, -1)) {
|
|
opt.name = wxString(lua_tostring(L, -1), wxConvUTF8);
|
|
lua_pop(L, 1);
|
|
} else {
|
|
lua_pop(L, 1);
|
|
// no name means invalid option
|
|
opt.kind = COK_INVALID;
|
|
goto continue_invalid_option;
|
|
}
|
|
|
|
// label
|
|
lua_pushstring(L, "label");
|
|
lua_gettable(L, -2);
|
|
if (lua_isstring(L, -1)) {
|
|
opt.label = wxString(lua_tostring(L, -1), wxConvUTF8);
|
|
lua_pop(L, 1);
|
|
} else {
|
|
lua_pop(L, 1);
|
|
// label is also required
|
|
opt.kind = COK_INVALID;
|
|
goto continue_invalid_option;
|
|
}
|
|
assert(opt.kind != COK_INVALID);
|
|
|
|
// hint
|
|
lua_pushstring(L, "hint");
|
|
lua_gettable(L, -2);
|
|
if (lua_isstring(L, -1)) {
|
|
opt.hint = wxString(lua_tostring(L, -1), wxConvUTF8);
|
|
} else {
|
|
opt.hint = _T("");
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
// min
|
|
lua_pushstring(L, "min");
|
|
lua_gettable(L, -2);
|
|
if (lua_isnumber(L, -1)) {
|
|
opt.min.isset = true;
|
|
opt.min.floatval = lua_tonumber(L, -1);
|
|
opt.min.intval = (int)opt.min.floatval;
|
|
} else {
|
|
opt.min.isset = false;
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
// max
|
|
lua_pushstring(L, "max");
|
|
lua_gettable(L, -2);
|
|
if (lua_isnumber(L, -1)) {
|
|
opt.max.isset = true;
|
|
opt.max.floatval = lua_tonumber(L, -1);
|
|
opt.max.intval = (int)opt.max.floatval;
|
|
} else {
|
|
opt.max.isset = false;
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
// default (this is going to kill me)
|
|
lua_pushstring(L, "default");
|
|
lua_gettable(L, -2);
|
|
switch (opt.kind) {
|
|
case COK_LABEL:
|
|
// nothing to do, nothing expected
|
|
break;
|
|
case COK_TEXT:
|
|
case COK_STYLE:
|
|
// expect it to be a string
|
|
if (lua_isstring(L, -1)) {
|
|
opt.default_val.stringval = wxString(lua_tostring(L, -1), wxConvUTF8);
|
|
} else {
|
|
// not a string, baaaad scripter
|
|
opt.kind = COK_INVALID;
|
|
}
|
|
break;
|
|
case COK_INT:
|
|
case COK_FLOAT:
|
|
// expect it to be a number
|
|
if (lua_isnumber(L, -1)) {
|
|
opt.default_val.floatval = lua_tonumber(L, -1);
|
|
opt.default_val.intval = (int)opt.default_val.floatval;
|
|
} else {
|
|
opt.kind = COK_INVALID;
|
|
}
|
|
break;
|
|
case COK_BOOL:
|
|
// expect it to be a bool
|
|
if (lua_isboolean(L, -1)) {
|
|
opt.default_val.boolval = lua_toboolean(L, -1)!=0;
|
|
} else {
|
|
opt.kind = COK_INVALID;
|
|
}
|
|
break;
|
|
case COK_COLOUR:
|
|
// expect it to be a ass hex colour formatted string
|
|
if (lua_isstring(L, -1)) {
|
|
opt.default_val.stringval = wxString(lua_tostring(L, -1), wxConvUTF8);
|
|
opt.default_val.colourval.Parse(opt.default_val.stringval); // and hope this goes well!
|
|
} else {
|
|
opt.kind = COK_INVALID;
|
|
}
|
|
break;
|
|
}
|
|
opt.value = opt.default_val;
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
// so we successfully got an option added, so at least there is a configuration present now
|
|
present = true;
|
|
continue_invalid_option:
|
|
// clean up and prepare for next iteration
|
|
lua_pop(L, 1);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
Auto3ConfigDialog::~Auto3ConfigDialog()
|
|
{
|
|
// TODO?
|
|
}
|
|
|
|
int Auto3ConfigDialog::LuaReadBack(lua_State *L)
|
|
{
|
|
lua_newtable(L);
|
|
|
|
for (std::vector<Auto3ScriptConfigurationOption>::iterator opt = options.begin(); opt != options.end(); opt++) {
|
|
switch (opt->kind) {
|
|
case COK_INVALID:
|
|
case COK_LABEL:
|
|
break;
|
|
|
|
case COK_TEXT:
|
|
case COK_STYLE:
|
|
L_settable(L, -1, opt->name, opt->value.stringval);
|
|
break;
|
|
|
|
case COK_INT:
|
|
L_settable(L, -1, opt->name, opt->value.intval);
|
|
break;
|
|
|
|
case COK_FLOAT:
|
|
L_settable(L, -1, opt->name, opt->value.floatval);
|
|
break;
|
|
|
|
case COK_BOOL:
|
|
L_settable_bool(L, -1, opt->name, opt->value.boolval);
|
|
break;
|
|
|
|
case COK_COLOUR:
|
|
L_settable(L, -1, opt->name, opt->value.colourval.GetASSFormatted(false, false));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void Auto3ConfigDialog::ReadBack()
|
|
{
|
|
wxString opthname = wxString::Format(_T("Automation Settings %s"), ident.c_str());
|
|
|
|
for (std::vector<Control>::iterator ctl = controls.begin(); ctl != controls.end(); ctl++) {
|
|
switch (ctl->option->kind) {
|
|
case COK_TEXT:
|
|
ctl->option->value.stringval = ((wxTextCtrl*)ctl->control)->GetValue();
|
|
break;
|
|
|
|
case COK_INT:
|
|
ctl->option->value.intval = ((wxSpinCtrl*)ctl->control)->GetValue();
|
|
break;
|
|
|
|
case COK_FLOAT:
|
|
if (!((wxTextCtrl*)ctl->control)->GetValue().ToDouble(&ctl->option->value.floatval)) {
|
|
wxLogWarning(
|
|
_T("The value entered for field '%s' (%s) could not be converted to a floating-point number. Default value (%f) substituted for the entered value."),
|
|
ctl->option->label.c_str(),
|
|
((wxTextCtrl*)ctl->control)->GetValue().c_str(),
|
|
ctl->option->default_val.floatval);
|
|
ctl->option->value.floatval = ctl->option->default_val.floatval;
|
|
}
|
|
break;
|
|
|
|
case COK_BOOL:
|
|
ctl->option->value.boolval = ((wxCheckBox*)ctl->control)->GetValue();
|
|
break;
|
|
|
|
case COK_COLOUR:
|
|
// *FIXME* needs to be updated to use a proper color control
|
|
ctl->option->value.colourval.Parse(((wxTextCtrl*)ctl->control)->GetValue());
|
|
break;
|
|
|
|
case COK_STYLE:
|
|
ctl->option->value.stringval = ((wxChoice*)ctl->control)->GetStringSelection();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// serialize the new settings and save them to the file
|
|
AssFile::top->SetScriptInfo(opthname, serialize());
|
|
}
|
|
|
|
wxString Auto3ConfigDialog::serialize()
|
|
{
|
|
if (options.size() == 0)
|
|
return _T("");
|
|
|
|
wxString result;
|
|
for (std::vector<Auto3ScriptConfigurationOption>::iterator opt = options.begin(); opt != options.end(); opt++) {
|
|
switch (opt->kind) {
|
|
case COK_TEXT:
|
|
case COK_STYLE:
|
|
result << wxString::Format(_T("%s:%s|"), opt->name.c_str(), inline_string_encode(opt->value.stringval).c_str());
|
|
break;
|
|
case COK_INT:
|
|
result << wxString::Format(_T("%s:%d|"), opt->name.c_str(), opt->value.intval);
|
|
break;
|
|
case COK_FLOAT:
|
|
result << wxString::Format(_T("%s:%e|"), opt->name.c_str(), opt->value.floatval);
|
|
break;
|
|
case COK_BOOL:
|
|
result << wxString::Format(_T("%s:%d|"), opt->name.c_str(), opt->value.boolval?1:0);
|
|
break;
|
|
case COK_COLOUR:
|
|
result << wxString::Format(_T("%s:%s|"), opt->name.c_str(), opt->value.colourval.GetASSFormatted(false).c_str());
|
|
break;
|
|
default:
|
|
// The rest aren't stored
|
|
break;
|
|
}
|
|
}
|
|
if (result.Last() == _T('|'))
|
|
result.RemoveLast();
|
|
return result;
|
|
}
|
|
|
|
void Auto3ConfigDialog::unserialize(wxString &settings)
|
|
{
|
|
wxStringTokenizer toker(settings, _T("|"), wxTOKEN_STRTOK);
|
|
while (toker.HasMoreTokens()) {
|
|
// get the parts of this setting
|
|
wxString setting = toker.GetNextToken();
|
|
|
|
wxString optname = setting.BeforeFirst(_T(':'));
|
|
wxString optval = setting.AfterFirst(_T(':'));
|
|
|
|
// find the setting in the list loaded from the script
|
|
std::vector<Auto3ScriptConfigurationOption>::iterator opt = options.begin();
|
|
while (opt != options.end() && opt->name != optname)
|
|
opt ++;
|
|
|
|
if (opt != options.end()) {
|
|
// ok, found the option!
|
|
switch (opt->kind) {
|
|
case COK_TEXT:
|
|
case COK_STYLE:
|
|
opt->value.stringval = inline_string_decode(optval);
|
|
break;
|
|
|
|
case COK_INT:
|
|
{
|
|
long n;
|
|
optval.ToLong(&n, 10);
|
|
opt->value.intval = n;
|
|
}
|
|
break;
|
|
|
|
case COK_FLOAT:
|
|
optval.ToDouble(&opt->value.floatval);
|
|
break;
|
|
|
|
case COK_BOOL:
|
|
opt->value.boolval = optval == _T("1");
|
|
break;
|
|
|
|
case COK_COLOUR:
|
|
opt->value.colourval.Parse(optval);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Auto3Filter
|
|
|
|
Auto3Filter::Auto3Filter(const wxString &_name, const wxString &_description, lua_State *_L)
|
|
: Feature(SCRIPTFEATURE_FILTER, _name)
|
|
, FeatureFilter(_name, _description, 0)
|
|
, L(_L)
|
|
{
|
|
// check that the processing function exists
|
|
lua_getglobal(L, "process_lines");
|
|
if (!lua_isfunction(L, -1)) {
|
|
throw _T("Script error: No 'process_lines' function provided");
|
|
}
|
|
|
|
// configuration (let the config object do all the loading)
|
|
lua_getglobal(L, "configuration");
|
|
config = new Auto3ConfigDialog(L, GetName());
|
|
|
|
lua_pop(L, 2);
|
|
}
|
|
|
|
ScriptConfigDialog* Auto3Filter::GenerateConfigDialog(wxWindow *parent)
|
|
{
|
|
return config;
|
|
}
|
|
|
|
void Auto3Filter::Init()
|
|
{
|
|
// Nothing to do here
|
|
}
|
|
|
|
void Auto3Filter::ProcessSubs(AssFile *subs, wxWindow *export_dialog)
|
|
{
|
|
Auto3ProgressSink *sink = new Auto3ProgressSink(L, export_dialog);
|
|
sink->SetTitle(GetName());
|
|
Auto3ThreadedProcessor thread(L, subs, config, sink);
|
|
|
|
sink->ShowModal();
|
|
thread.Wait();
|
|
|
|
delete sink;
|
|
}
|
|
|
|
|
|
// Auto3ThreadedProcessor
|
|
|
|
Auto3ThreadedProcessor::Auto3ThreadedProcessor(lua_State *_L, AssFile *_file, Auto3ConfigDialog *_config, Auto3ProgressSink *_sink)
|
|
: wxThread(wxTHREAD_JOINABLE)
|
|
, L(_L)
|
|
, file(_file)
|
|
, config(_config)
|
|
, sink(_sink)
|
|
{
|
|
// Pure copypasta
|
|
int prio = Options.AsInt(_T("Automation Thread Priority"));
|
|
if (prio == 0) prio = 50; // normal
|
|
else if (prio == 1) prio = 30; // below normal
|
|
else if (prio == 2) prio = 10; // lowest
|
|
else prio = 50; // fallback normal
|
|
Create();
|
|
SetPriority(prio);
|
|
Run();
|
|
}
|
|
|
|
wxThread::ExitCode Auto3ThreadedProcessor::Entry()
|
|
{
|
|
bool failed = false;
|
|
|
|
try {
|
|
sink->SetTask(_T("Preparing subtitle data"));
|
|
sink->SetProgress(0);
|
|
|
|
// first put the function itself on the stack
|
|
lua_pushstring(L, "process_lines");
|
|
lua_gettable(L, LUA_GLOBALSINDEX);
|
|
|
|
// now put the three arguments on the stack
|
|
|
|
// first argument: the metadata table
|
|
lua_newtable(L);
|
|
L_settable(L, -1, "res_x", file->GetScriptInfoAsInt(_T("PlayResX")));
|
|
L_settable(L, -1, "res_y", file->GetScriptInfoAsInt(_T("PlayResY")));
|
|
|
|
// second and third arguments: styles and events tables
|
|
lua_newtable(L);
|
|
int styletab = lua_gettop(L);
|
|
lua_newtable(L);
|
|
int eventtab = lua_gettop(L);
|
|
|
|
int numstyles = 0, numevents = 0;
|
|
|
|
// fill the styles and events tables
|
|
int processed_lines = 1;
|
|
for (std::list<AssEntry*>::iterator i = file->Line.begin(); i != file->Line.end(); i++, processed_lines++) {
|
|
|
|
AssEntry *e = *i;
|
|
|
|
if (!e->Valid) continue;
|
|
|
|
if (e->GetType() == ENTRY_STYLE) {
|
|
|
|
AssStyle *style = e->GetAsStyle(e);
|
|
|
|
// gonna need a table to put the style data into
|
|
lua_newtable(L);
|
|
// put the table into index N in the style table
|
|
lua_pushvalue(L, -1);
|
|
lua_rawseti(L, styletab, numstyles);
|
|
// and put it into its named index
|
|
lua_pushstring(L, style->name.mb_str(wxConvUTF8));
|
|
lua_pushvalue(L, -2);
|
|
lua_settable(L, styletab);
|
|
|
|
// so now the table is regged and stuff, put some data into it
|
|
L_settable (L, -1, "name", style->name);
|
|
L_settable (L, -1, "fontname", style->font);
|
|
L_settable (L, -1, "fontsize", style->fontsize);
|
|
L_settable (L, -1, "color1", style->primary.GetASSFormatted(true, true));
|
|
L_settable (L, -1, "color2", style->secondary.GetASSFormatted(true, true));
|
|
L_settable (L, -1, "color3", style->outline.GetASSFormatted(true, true));
|
|
L_settable (L, -1, "color4", style->shadow.GetASSFormatted(true, true));
|
|
L_settable_bool(L, -1, "bold", style->bold);
|
|
L_settable_bool(L, -1, "italic", style->italic);
|
|
L_settable_bool(L, -1, "underline", style->underline);
|
|
L_settable_bool(L, -1, "strikeout", style->strikeout);
|
|
L_settable (L, -1, "scale_x", style->scalex);
|
|
L_settable (L, -1, "scale_y", style->scaley);
|
|
L_settable (L, -1, "spacing", style->spacing);
|
|
L_settable (L, -1, "angle", style->angle);
|
|
L_settable (L, -1, "borderstyle", style->borderstyle);
|
|
L_settable (L, -1, "outline", style->outline_w);
|
|
L_settable (L, -1, "shadow", style->shadow_w);
|
|
L_settable (L, -1, "align", style->alignment);
|
|
L_settable (L, -1, "margin_l", style->Margin[0]);
|
|
L_settable (L, -1, "margin_r", style->Margin[1]);
|
|
L_settable (L, -1, "margin_v", style->Margin[2]);
|
|
L_settable (L, -1, "encoding", style->encoding);
|
|
|
|
// and get that table off the stack again
|
|
lua_settop(L, -2);
|
|
|
|
numstyles++;
|
|
|
|
} else if (e->group == _T("[Events]")) {
|
|
|
|
if (e->GetType() != ENTRY_DIALOGUE) {
|
|
|
|
// not a dialogue/comment event
|
|
|
|
// start checking for a blank line
|
|
wxString entryData = e->GetEntryData();
|
|
if (entryData.IsEmpty()) {
|
|
lua_newtable(L);
|
|
L_settable(L, -1, "kind", wxString(_T("blank")));
|
|
} else if (entryData[0] == _T(';')) {
|
|
// semicolon comment
|
|
lua_newtable(L);
|
|
L_settable(L, -1, "kind", wxString(_T("scomment")));
|
|
L_settable(L, -1, "text", entryData.Mid(1));
|
|
} else {
|
|
// not a blank line and not a semicolon comment
|
|
// just skip...
|
|
continue;
|
|
}
|
|
|
|
} else {
|
|
|
|
// ok, so it is a dialogue/comment event
|
|
// massive handling :(
|
|
|
|
lua_newtable(L);
|
|
|
|
assert(e->GetType() == ENTRY_DIALOGUE);
|
|
|
|
AssDialogue *dia = e->GetAsDialogue(e);
|
|
|
|
// kind of line
|
|
if (dia->Comment) {
|
|
L_settable(L, -1, "kind", wxString(_T("comment")));
|
|
} else {
|
|
L_settable(L, -1, "kind", wxString(_T("dialogue")));
|
|
}
|
|
|
|
L_settable(L, -1, "layer", dia->Layer);
|
|
L_settable(L, -1, "start_time", dia->Start.GetMS()/10);
|
|
L_settable(L, -1, "end_time", dia->End.GetMS()/10);
|
|
L_settable(L, -1, "style", dia->Style);
|
|
L_settable(L, -1, "name", dia->Actor);
|
|
L_settable(L, -1, "margin_l", dia->Margin[0]);
|
|
L_settable(L, -1, "margin_r", dia->Margin[1]);
|
|
L_settable(L, -1, "margin_v", dia->Margin[2]);
|
|
L_settable(L, -1, "effect", dia->Effect);
|
|
L_settable(L, -1, "text", dia->Text);
|
|
|
|
// so that's the easy part
|
|
// now for the stripped text and *ugh* the karaoke!
|
|
|
|
// prepare for stripped text
|
|
wxString text_stripped = _T("");
|
|
L_settable(L, -1, "text_stripped", 0); // dummy item
|
|
// prepare karaoke table
|
|
lua_newtable(L);
|
|
lua_pushstring(L, "karaoke");
|
|
lua_pushvalue(L, -2);
|
|
lua_settable(L, -4);
|
|
// now the top of the stack is the karaoke table, and it's present in the dialogue table
|
|
|
|
int kcount = 0;
|
|
int kdur = 0;
|
|
wxString kkind = _T("");
|
|
wxString ktext = _T("");
|
|
wxString ktext_stripped = _T("");
|
|
|
|
dia->ParseASSTags();
|
|
for (std::vector<AssDialogueBlock*>::iterator block = dia->Blocks.begin(); block != dia->Blocks.end(); block++) {
|
|
|
|
switch ((*block)->type) {
|
|
|
|
case BLOCK_BASE:
|
|
lua_pushliteral(L, "BLOCK_BASE found processing dialogue blocks. This should never happen.");
|
|
lua_error(L);
|
|
break;
|
|
|
|
case BLOCK_PLAIN:
|
|
ktext += (*block)->text;
|
|
ktext_stripped += (*block)->text;
|
|
text_stripped += (*block)->text;
|
|
break;
|
|
|
|
case BLOCK_DRAWING:
|
|
ktext += (*block)->text;
|
|
break;
|
|
|
|
case BLOCK_OVERRIDE: {
|
|
bool brackets_open = false;
|
|
std::vector<AssOverrideTag*> &tags = (*block)->GetAsOverride(*block)->Tags;
|
|
|
|
for (std::vector<AssOverrideTag*>::iterator tag = tags.begin(); tag != tags.end(); tag++) {
|
|
|
|
if (!(*tag)->Name.Mid(0,2).CmpNoCase(_T("\\k")) && (*tag)->IsValid()) {
|
|
|
|
// it's a karaoke tag
|
|
if (brackets_open) {
|
|
ktext += _T("}");
|
|
brackets_open = false;
|
|
}
|
|
L_settable_kara(L, -1, kcount, kdur, kkind, ktext, ktext_stripped);
|
|
kcount++;
|
|
kdur = (*tag)->Params[0]->AsInt(); // no error checking; this should always be int
|
|
kkind = (*tag)->Name.Mid(1);
|
|
ktext = _T("");
|
|
ktext_stripped = _T("");
|
|
|
|
} else {
|
|
|
|
// it's something else
|
|
// don't care if it's a valid tag or not
|
|
if (!brackets_open) {
|
|
ktext += _T("{");
|
|
brackets_open = true;
|
|
}
|
|
ktext += (*tag)->ToString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (brackets_open) {
|
|
ktext += _T("}");
|
|
}
|
|
|
|
break;}
|
|
|
|
}
|
|
|
|
}
|
|
dia->ClearBlocks();
|
|
|
|
// add the final karaoke block to the table
|
|
// (even if there's no karaoke in the line, there's always at least one karaoke block)
|
|
// even if the line ends in {\k10} with no text after, an empty block should still be inserted
|
|
// (otherwise data are lost)
|
|
L_settable_kara(L, -1, kcount, kdur, kkind, ktext, ktext_stripped);
|
|
kcount++;
|
|
L_settable(L, -1, "n", kcount); // number of syllables in the karaoke
|
|
lua_settop(L, -2); // remove karaoke table from the stack again
|
|
L_settable(L, -1, "text_stripped", text_stripped); // store the real stripped text
|
|
|
|
}
|
|
|
|
// now the entry table has been created and placed on top of the stack
|
|
// now all that's missing it to insert it into the event table
|
|
lua_rawseti(L, eventtab, numevents);
|
|
numevents++;
|
|
|
|
} else {
|
|
// not really a line type automation needs to take care of... ignore it
|
|
}
|
|
|
|
sink->SetProgress(100.0f * processed_lines / file->Line.size() / 3);
|
|
|
|
}
|
|
|
|
// finally add the counter elements to the styles and events tables
|
|
lua_pushnumber(L, numstyles);
|
|
lua_rawseti(L, styletab, -1);
|
|
L_settable(L, eventtab, "n", numevents);
|
|
// and let the config object create a table for the @config argument
|
|
config->LuaReadBack(L);
|
|
|
|
sink->SetTask(_T("Running script for processing"));
|
|
sink->SetProgress(100.0f/3);
|
|
lua_call(L, 4, 1);
|
|
sink->SetProgress(200.0f/3);
|
|
sink->SetTask(_T("Reading back data from script"));
|
|
|
|
// phew, survived the call =)
|
|
// time to read back the results
|
|
|
|
if (!lua_istable(L, -1)) {
|
|
throw _T("The script function did not return a table as expected. Unable to process results. (Nothing was changed.)");
|
|
}
|
|
|
|
// but start by removing all events
|
|
{
|
|
std::list<AssEntry*>::iterator cur, next;
|
|
next = file->Line.begin();
|
|
while (next != file->Line.end()) {
|
|
cur = next++;
|
|
if ((*cur)->group == _T("[Events]")) {
|
|
wxString temp = (*cur)->GetEntryData();
|
|
if (temp == _T("[Events]")) {
|
|
// skip the section header
|
|
continue;
|
|
}
|
|
if ((*cur)->GetType() != ENTRY_DIALOGUE && temp.Mid(0,1) != _T(";") && temp.Trim() != _T("")) {
|
|
// skip non-dialogue non-semicolon comment lines (such as Format)
|
|
continue;
|
|
}
|
|
delete (*cur);
|
|
file->Line.erase(cur);
|
|
}
|
|
}
|
|
}
|
|
|
|
// so anyway, there is a single table on the stack now
|
|
// that table contains a lot of events...
|
|
// and it ought to contain an "n" key as well, telling how many events
|
|
// but be lenient, and don't expect one to be there, but rather count from zero and let it be nil-terminated
|
|
// if the "n" key is there, use it as a progress indicator hint, though
|
|
int output_line_count;
|
|
lua_pushstring(L, "n");
|
|
lua_gettable(L, -2);
|
|
if (lua_isnumber(L, -1)) {
|
|
output_line_count = (int) lua_tonumber(L, -1);
|
|
} else {
|
|
// assume number of output lines == number of input lines
|
|
output_line_count = processed_lines;
|
|
}
|
|
lua_settop(L, -2);
|
|
|
|
int outline = 0;
|
|
int faketime = file->Line.back()->StartMS;
|
|
|
|
// If there's nothing at index 0, start at index 1 instead, to support both zero and one based indexing
|
|
lua_pushnumber(L, outline);
|
|
lua_gettable(L, -2);
|
|
if (!lua_istable(L, -1)) {
|
|
outline++;
|
|
output_line_count++;
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
while (lua_pushnumber(L, outline), lua_gettable(L, -2), lua_istable(L, -1)) {
|
|
// top of the stack is a table, hopefully with an AssEntry in it
|
|
|
|
// start by getting the kind
|
|
lua_pushstring(L, "kind");
|
|
lua_gettable(L, -2);
|
|
if (!lua_isstring(L, -1)) {
|
|
sink->AddDebugOutput(wxString::Format(_T("The output data at index %d is mising a valid 'kind' field, and has been skipped\n"), outline));
|
|
lua_settop(L, -2);
|
|
} else {
|
|
|
|
wxString kind = wxString(lua_tostring(L, -1), wxConvUTF8).Lower();
|
|
// remove "kind" from stack again
|
|
lua_settop(L, -2);
|
|
|
|
if (kind == _T("dialogue") || kind == _T("comment")) {
|
|
|
|
lua_pushstring(L, "layer");
|
|
lua_gettable(L, -2);
|
|
lua_pushstring(L, "start_time");
|
|
lua_gettable(L, -3);
|
|
lua_pushstring(L, "end_time");
|
|
lua_gettable(L, -4);
|
|
lua_pushstring(L, "style");
|
|
lua_gettable(L, -5);
|
|
lua_pushstring(L, "name");
|
|
lua_gettable(L, -6);
|
|
lua_pushstring(L, "margin_l");
|
|
lua_gettable(L, -7);
|
|
lua_pushstring(L, "margin_r");
|
|
lua_gettable(L, -8);
|
|
lua_pushstring(L, "margin_v");
|
|
lua_gettable(L, -9);
|
|
lua_pushstring(L, "effect");
|
|
lua_gettable(L, -10);
|
|
lua_pushstring(L, "text");
|
|
lua_gettable(L, -11);
|
|
|
|
if (lua_isnumber(L, -10) && lua_isnumber(L, -9) && lua_isnumber(L, -8) &&
|
|
lua_isstring(L, -7) && lua_isstring(L, -6) && lua_isnumber(L, -5) &&
|
|
lua_isnumber(L, -4) && lua_isnumber(L, -3) && lua_isstring(L, -2) &&
|
|
lua_isstring(L, -1))
|
|
{
|
|
AssDialogue *e = new AssDialogue();
|
|
e->Layer = (int)lua_tonumber(L, -10);
|
|
e->Start.SetMS(10*(int)lua_tonumber(L, -9));
|
|
e->End.SetMS(10*(int)lua_tonumber(L, -8));
|
|
e->Style = wxString(lua_tostring(L, -7), wxConvUTF8);
|
|
e->Actor = wxString(lua_tostring(L, -6), wxConvUTF8);
|
|
e->Margin[0] = (int)lua_tonumber(L, -5);
|
|
e->Margin[1] = (int)lua_tonumber(L, -4);
|
|
e->Margin[2] = e->Margin[3] = (int)lua_tonumber(L, -3);
|
|
e->Effect = wxString(lua_tostring(L, -2), wxConvUTF8);
|
|
e->Text = wxString(lua_tostring(L, -1), wxConvUTF8);
|
|
e->Comment = kind == _T("comment");
|
|
lua_settop(L, -11);
|
|
e->StartMS = e->Start.GetMS();
|
|
//e->ParseASSTags();
|
|
e->UpdateData();
|
|
file->Line.push_back(e);
|
|
} else {
|
|
sink->AddDebugOutput(wxString::Format(_T("The output data at index %d (kind '%s') has one or more missing/invalid fields, and has been skipped\n"), outline, kind.c_str()));
|
|
}
|
|
|
|
} else if (kind == _T("scomment")) {
|
|
|
|
lua_pushstring(L, "text");
|
|
lua_gettable(L, -2);
|
|
if (lua_isstring(L, -1)) {
|
|
wxString text(lua_tostring(L, -1), wxConvUTF8);
|
|
lua_settop(L, -2);
|
|
AssEntry *e = new AssEntry(wxString(_T(";")) + text);
|
|
e->StartMS = faketime;
|
|
file->Line.push_back(e);
|
|
} else {
|
|
sink->AddDebugOutput(wxString::Format(_T("The output data at index %d (kind 'scomment') is missing a valid 'text' field, and has been skipped\n"), outline));
|
|
}
|
|
|
|
} else if (kind == _T("blank")) {
|
|
|
|
AssEntry *e = new AssEntry(_T(""));
|
|
e->StartMS = faketime;
|
|
file->Line.push_back(e);
|
|
|
|
} else {
|
|
sink->AddDebugOutput(wxString::Format(_T("The output data at index %d has an invalid value in the 'kind' field, and has been skipped\n"), outline));
|
|
}
|
|
|
|
}
|
|
|
|
// remove table again
|
|
lua_settop(L, -2);
|
|
// progress report
|
|
if (outline >= output_line_count) {
|
|
sink->SetProgress(99.9f);
|
|
} else {
|
|
sink->SetProgress((200.0f + 100.0f*outline/output_line_count) / 3);
|
|
}
|
|
|
|
outline++;
|
|
}
|
|
|
|
sink->SetTask(_T("Completed"));
|
|
}
|
|
catch (const wchar_t *e) {
|
|
failed = true;
|
|
sink->AddDebugOutput(e);
|
|
}
|
|
catch (const char *e) {
|
|
failed = true;
|
|
wxString s(e, wxConvUTF8);
|
|
sink->AddDebugOutput(s);
|
|
}
|
|
catch (...) {
|
|
failed = true;
|
|
if (lua_isstring(L, -1)) {
|
|
wxString s(lua_tostring(L, -1), wxConvUTF8);
|
|
sink->AddDebugOutput(s);
|
|
} else {
|
|
sink->AddDebugOutput(_T("Unknown error"));
|
|
}
|
|
}
|
|
|
|
if (failed) {
|
|
sink->SetTask(_T("Failed"));
|
|
} else {
|
|
sink->SetProgress(100);
|
|
}
|
|
sink->script_finished = true;
|
|
wxWakeUpIdle();
|
|
if (failed) {
|
|
return (wxThread::ExitCode)1;
|
|
} else {
|
|
return (wxThread::ExitCode)0;
|
|
}
|
|
}
|
|
|
|
|
|
// Auto3Script
|
|
|
|
Auto3Script::Auto3Script(const wxString &filename)
|
|
: Script(filename)
|
|
, L(0)
|
|
, filter(0)
|
|
{
|
|
try {
|
|
Create();
|
|
}
|
|
catch (wxChar *e) {
|
|
description = e;
|
|
loaded = false;
|
|
}
|
|
}
|
|
|
|
Auto3Script::~Auto3Script()
|
|
{
|
|
if (L) Destroy();
|
|
}
|
|
|
|
Auto3Script* Auto3Script::GetScriptObject(lua_State *L)
|
|
{
|
|
lua_getfield(L, LUA_REGISTRYINDEX, "aegisub");
|
|
void *ptr = lua_touserdata(L, -1);
|
|
lua_pop(L, 1);
|
|
return (Auto3Script*)ptr;
|
|
}
|
|
|
|
int Auto3Script::LuaTextExtents(lua_State *L)
|
|
{
|
|
double resx, resy, resd, resl;
|
|
|
|
wxString intext(lua_tostring(L, -1), wxConvUTF8);
|
|
|
|
AssStyle st;
|
|
|
|
st.font = L_gettableS(L, "fontname");
|
|
st.fontsize = L_gettableN(L, "fontsize");
|
|
st.bold = L_gettableB(L, "bold");
|
|
st.italic = L_gettableB(L, "italic");
|
|
st.underline = L_gettableB(L, "underline");
|
|
st.strikeout = L_gettableB(L, "strikeout");
|
|
st.scalex = L_gettableN(L, "scale_x");
|
|
st.scaley = L_gettableN(L, "scale_y");
|
|
st.spacing = (int)L_gettableN(L, "spacing");
|
|
st.encoding = (int)L_gettableN(L, "encoding");
|
|
|
|
if (!CalculateTextExtents(&st, intext, resx, resy, resd, resl)) {
|
|
lua_pushstring(L, "Some internal error occurred calculating text_extents");
|
|
lua_error(L);
|
|
}
|
|
|
|
lua_pushnumber(L, resx);
|
|
lua_pushnumber(L, resy);
|
|
lua_pushnumber(L, resd);
|
|
lua_pushnumber(L, resl);
|
|
return 4;
|
|
}
|
|
|
|
int Auto3Script::LuaInclude(lua_State *L)
|
|
{
|
|
Auto3Script *s = GetScriptObject(L);
|
|
|
|
if (!lua_isstring(L, 1)) {
|
|
lua_pushstring(L, "Argument to include must be a string");
|
|
lua_error(L);
|
|
return 0;
|
|
}
|
|
wxString fnames(lua_tostring(L, 1), wxConvUTF8);
|
|
|
|
wxFileName fname(fnames);
|
|
if (fname.GetDirCount() == 0) {
|
|
// filename only
|
|
fname = s->include_path.FindAbsoluteValidPath(fnames);
|
|
} else if (fname.IsRelative()) {
|
|
// relative path
|
|
wxFileName sfname(s->GetFilename());
|
|
fname.MakeAbsolute(sfname.GetPath(true));
|
|
} else {
|
|
// absolute path, do nothing
|
|
}
|
|
if (!fname.IsOk() || !fname.FileExists()) {
|
|
lua_pushfstring(L, "Could not find Automation 3 script for inclusion: %s", fnames.mb_str(wxConvUTF8).data());
|
|
lua_error(L);
|
|
}
|
|
|
|
LuaScriptReader script_reader(fname.GetFullPath());
|
|
if (lua_load(L, script_reader.reader_func, &script_reader, s->GetFilename().mb_str(wxConvUTF8))) {
|
|
lua_pushfstring(L, "An error occurred loading the Automation 3 script file \"%s\":\n\n%s", fname.GetFullPath().mb_str(wxConvUTF8).data(), lua_tostring(L, -1));
|
|
lua_error(L);
|
|
return 0;
|
|
}
|
|
int pretop = lua_gettop(L) - 1; // don't count the function value itself
|
|
lua_call(L, 0, LUA_MULTRET);
|
|
return lua_gettop(L) - pretop;
|
|
}
|
|
|
|
int Auto3Script::LuaColorstringToRGB(lua_State *L)
|
|
{
|
|
if (lua_gettop(L) < 1) {
|
|
lua_pushstring(L, "colorstring_to_rgb called without arguments");
|
|
lua_error(L);
|
|
}
|
|
if (!lua_isstring(L, 1)) {
|
|
lua_pushstring(L, "colorstring_to_rgb requires a string type argument");
|
|
lua_error(L);
|
|
}
|
|
|
|
wxString colorstring(lua_tostring(L, -1), wxConvUTF8);
|
|
lua_pop(L, 1);
|
|
AssColor rgb;
|
|
rgb.Parse(colorstring);
|
|
lua_pushnumber(L, rgb.r);
|
|
lua_pushnumber(L, rgb.g);
|
|
lua_pushnumber(L, rgb.b);
|
|
lua_pushnumber(L, rgb.a);
|
|
return 4;
|
|
}
|
|
|
|
int Auto3Script::LuaFrameFromMs(lua_State *L)
|
|
{
|
|
int ms = (int)lua_tonumber(L, -1);
|
|
lua_pop(L, 1);
|
|
if (VFR_Output.IsLoaded()) {
|
|
lua_pushnumber(L, VFR_Output.GetFrameAtTime(ms, true));
|
|
return 1;
|
|
} else {
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int Auto3Script::LuaMsFromFrame(lua_State *L)
|
|
{
|
|
int frame = (int)lua_tonumber(L, -1);
|
|
lua_pop(L, 1);
|
|
if (VFR_Output.IsLoaded()) {
|
|
lua_pushnumber(L, VFR_Output.GetTimeAtFrame(frame, true));
|
|
return 1;
|
|
} else {
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
void Auto3Script::Create()
|
|
{
|
|
Destroy();
|
|
|
|
loaded = true;
|
|
|
|
try {
|
|
L = lua_open();
|
|
|
|
// register standard libs
|
|
lua_pushcfunction(L, luaopen_base); lua_call(L, 0, 0);
|
|
lua_pushcfunction(L, luaopen_package); lua_call(L, 0, 0);
|
|
lua_pushcfunction(L, luaopen_string); lua_call(L, 0, 0);
|
|
lua_pushcfunction(L, luaopen_table); lua_call(L, 0, 0);
|
|
lua_pushcfunction(L, luaopen_math); lua_call(L, 0, 0);
|
|
// dofile and loadfile are replaced with include
|
|
lua_pushnil(L);
|
|
lua_setglobal(L, "dofile");
|
|
lua_pushnil(L);
|
|
lua_setglobal(L, "loadfile");
|
|
lua_pushcfunction(L, LuaInclude);
|
|
lua_setglobal(L, "include");
|
|
|
|
// reference to the script object
|
|
lua_pushlightuserdata(L, this);
|
|
lua_setfield(L, LUA_REGISTRYINDEX, "aegisub");
|
|
|
|
// make "aegisub" table
|
|
lua_newtable(L);
|
|
// put helper functions in it
|
|
lua_pushcfunction(L, LuaColorstringToRGB);
|
|
lua_setfield(L, -2, "colorstring_to_rgb");
|
|
lua_pushcfunction(L, LuaTextExtents);
|
|
lua_setfield(L, -2, "text_extents");
|
|
lua_pushcfunction(L, LuaFrameFromMs);
|
|
lua_setfield(L, -2, "frame_from_ms");
|
|
lua_pushcfunction(L, LuaMsFromFrame);
|
|
lua_setfield(L, -2, "ms_from_frame");
|
|
lua_pushinteger(L, 3);
|
|
lua_setfield(L, -2, "lua_automation_version");
|
|
// store table
|
|
lua_setfield(L, LUA_GLOBALSINDEX, "aegisub");
|
|
|
|
// load user script
|
|
LuaScriptReader script_reader(GetFilename());
|
|
if (lua_load(L, script_reader.reader_func, &script_reader, GetFilename().mb_str(wxConvUTF8))) {
|
|
wxString *err = new wxString(lua_tostring(L, -1), wxConvUTF8);
|
|
err->Prepend(_T("An error occurred loading the Automation 3 script file \"") + GetFilename() + _T("\":\n\n"));
|
|
throw err->c_str();
|
|
}
|
|
// and run it
|
|
{
|
|
int err = lua_pcall(L, 0, 0, 0);
|
|
if (err) {
|
|
// error occurred, assumed to be on top of Lua stack
|
|
wxString *errs = new wxString(lua_tostring(L, -1), wxConvUTF8);
|
|
errs->Prepend(_T("An error occurred initialising the Automation 3 script file \"") + GetFilename() + _T("\":\n\n"));
|
|
throw errs->c_str();
|
|
}
|
|
}
|
|
|
|
// so, the script should be loaded
|
|
// now try to get the script data!
|
|
// first the version
|
|
lua_getglobal(L, "version");
|
|
if (!lua_isnumber(L, -1)) {
|
|
throw _T("Script error: 'version' value not found or not a number");
|
|
}
|
|
double engineversion = lua_tonumber(L, -1);
|
|
if (engineversion < 3 || engineversion > 4) {
|
|
// invalid version
|
|
throw _T("Script error: 'version' must be 3 for Automation 3 scripts");
|
|
}
|
|
version = _T("");
|
|
// skip 'kind', it's useless
|
|
// name
|
|
lua_getglobal(L, "name");
|
|
if (!lua_isstring(L, -1)) {
|
|
name = GetFilename();
|
|
} else {
|
|
name = wxString(lua_tostring(L, -1), wxConvUTF8);
|
|
}
|
|
// description (optional)
|
|
lua_getglobal(L, "description");
|
|
if (lua_isstring(L, -1)) {
|
|
description = wxString(lua_tostring(L, -1), wxConvUTF8);
|
|
} else {
|
|
description = _T("");
|
|
}
|
|
lua_pop(L, 4);
|
|
|
|
// create filter feature object, that will check for process_lines function and configuration
|
|
filter = new Auto3Filter(name, description, L);
|
|
}
|
|
catch (...) {
|
|
Destroy();
|
|
loaded = false;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void Auto3Script::Destroy()
|
|
{
|
|
if (!L) return;
|
|
|
|
if (filter) {
|
|
delete filter;
|
|
filter = 0;
|
|
}
|
|
|
|
lua_close(L);
|
|
L = 0;
|
|
|
|
loaded = false;
|
|
}
|
|
|
|
void Auto3Script::Reload()
|
|
{
|
|
Destroy();
|
|
Create();
|
|
}
|
|
|
|
|
|
// Auto3ScriptFactory
|
|
|
|
class Auto3ScriptFactory : public ScriptFactory {
|
|
public:
|
|
Auto3ScriptFactory()
|
|
{
|
|
engine_name = _T("Legacy Automation 3");
|
|
filename_pattern = _T("*.auto3");
|
|
Register(this);
|
|
}
|
|
|
|
~Auto3ScriptFactory() { }
|
|
|
|
virtual Script* Produce(const wxString &filename) const
|
|
{
|
|
if (filename.Right(4).Lower() == _T(".auto3")) {
|
|
return new Auto3Script(filename);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
};
|
|
Auto3ScriptFactory _script_factory;
|
|
|
|
};
|