32
32
/// @ingroup scripting
37
35
#include "auto4_lua.h"
39
#include "auto4_lua_utils.h"
40
37
#include "ass_dialogue.h"
41
38
#include "ass_file.h"
42
40
#include "ass_style.h"
41
#include "async_video_provider.h"
43
42
#include "auto4_lua_factory.h"
44
#include "auto4_lua_scriptreader.h"
43
#include "command/command.h"
45
44
#include "compat.h"
46
45
#include "include/aegisub/context.h"
48
46
#include "options.h"
49
48
#include "selection_controller.h"
50
49
#include "subs_controller.h"
51
#include "video_context.h"
50
#include "video_controller.h"
54
#include <libaegisub/access.h>
53
#include <libaegisub/format.h>
54
#include <libaegisub/lua/modules.h>
55
#include <libaegisub/lua/script_reader.h>
56
#include <libaegisub/lua/utils.h>
57
#include <libaegisub/make_unique.h>
55
58
#include <libaegisub/path.h>
56
#include <libaegisub/util.h>
58
60
#include <algorithm>
61
#include <boost/algorithm/string/case_conv.hpp>
59
62
#include <boost/algorithm/string/classification.hpp>
60
#include <boost/algorithm/string/join.hpp>
61
63
#include <boost/algorithm/string/predicate.hpp>
62
#include <boost/algorithm/string/replace.hpp>
63
#include <boost/format.hpp>
64
#include <boost/range/adaptor/reversed.hpp>
65
#include <boost/regex.hpp>
66
#include <boost/tokenizer.hpp>
64
#include <boost/scope_exit.hpp>
71
67
#include <wx/clipbrd.h>
72
#include <wx/filefn.h>
73
#include <wx/filename.h>
74
68
#include <wx/log.h>
75
69
#include <wx/msgdlg.h>
76
#include <wx/window.h>
71
using namespace agi::lua;
72
using namespace Automation4;
75
wxString get_wxstring(lua_State *L, int idx)
77
return wxString::FromUTF8(lua_tostring(L, idx));
80
wxString check_wxstring(lua_State *L, int idx)
82
return to_wx(check_string(L, idx));
79
85
void set_context(lua_State *L, const agi::Context *c)
81
87
// Explicit cast is needed to discard the const
148
154
int clipboard_init(lua_State *L)
151
set_field(L, "get", clipboard_get);
152
set_field(L, "set", clipboard_set);
156
lua_createtable(L, 0, 2);
157
set_field<clipboard_get>(L, "get");
158
set_field<clipboard_set>(L, "set");
157
static void init(lua_State *L) { lua_pushnil(L); }
158
static int next(lua_State *L) {
159
luaL_checktype(L, 1, LUA_TTABLE);
166
static const char *method() { return "__pairs"; }
169
struct table_ipairs {
170
static void init(lua_State *L) { push_value(L, 0); }
171
static int next(lua_State *L) {
172
luaL_checktype(L, 1, LUA_TTABLE);
173
int i = luaL_checkint(L, 2) + 1;
174
lua_pushinteger(L, i);
175
lua_rawgeti(L, 1, i);
176
return lua_isnil(L, -1) ? 0 : 2;
178
static const char *method() { return "__ipairs"; }
181
template<typename TableIter>
182
int pairs(lua_State *L) {
183
// If the metamethod is defined, call it instead
184
if (luaL_getmetafield(L, 1, TableIter::method())) {
190
// No metamethod, so use the table iterators
191
luaL_checktype(L, 1, LUA_TTABLE);
192
lua_pushcfunction(L, &TableIter::next);
198
162
int frame_from_ms(lua_State *L)
200
164
const agi::Context *c = get_context(L);
201
165
int ms = lua_tointeger(L, -1);
203
if (c && c->videoController->TimecodesLoaded())
167
if (c && c->project->Timecodes().IsLoaded())
204
168
push_value(L, c->videoController->FrameAtTime(ms, agi::vfr::START));
266
222
int cancel_script(lua_State *L)
272
228
int lua_text_textents(lua_State *L)
274
luaL_argcheck(L, lua_istable(L, 1), 1, "");
275
luaL_argcheck(L, lua_isstring(L, 2), 2, "");
230
argcheck(L, !!lua_istable(L, 1), 1, "");
231
argcheck(L, !!lua_isstring(L, 2), 2, "");
233
// have to check that it looks like a style table before actually converting
234
// if it's a dialogue table then an active AssFile object is required
236
lua_getfield(L, 1, "class");
237
std::string actual_class{lua_tostring(L, -1)};
238
boost::to_lower(actual_class);
239
if (actual_class != "style")
240
return error(L, "Not a style entry");
277
244
lua_pushvalue(L, 1);
278
245
std::unique_ptr<AssEntry> et(Automation4::LuaAssFile::LuaToAssEntry(L));
279
AssStyle *st = dynamic_cast<AssStyle*>(et.get());
246
auto st = dynamic_cast<AssStyle*>(et.get());
282
return luaL_error(L, "Not a style entry");
249
return error(L, "Not a style entry");
284
251
double width, height, descent, extlead;
285
if (!Automation4::CalculateTextExtents(st, luaL_checkstring(L, 2), width, height, descent, extlead))
286
return luaL_error(L, "Some internal error occurred calculating text_extents");
252
if (!Automation4::CalculateTextExtents(st, check_string(L, 2), width, height, descent, extlead))
253
return error(L, "Some internal error occurred calculating text_extents");
288
255
push_value(L, width);
289
256
push_value(L, height);
291
258
push_value(L, extlead);
296
extern "C" int luaopen_lpeg (lua_State *L);
298
namespace Automation4 {
299
int regex_init(lua_State *L);
301
class LuaScript : public Script {
262
int project_properties(lua_State *L)
264
const agi::Context *c = get_context(L);
268
lua_createtable(L, 0, 14);
269
#define PUSH_FIELD(name) set_field(L, #name, c->ass->Properties.name)
270
PUSH_FIELD(automation_scripts);
271
PUSH_FIELD(export_filters);
272
PUSH_FIELD(export_encoding);
273
PUSH_FIELD(style_storage);
274
PUSH_FIELD(video_zoom);
275
PUSH_FIELD(ar_value);
276
PUSH_FIELD(scroll_position);
277
PUSH_FIELD(active_row);
279
PUSH_FIELD(video_position);
281
set_field(L, "audio_file", config::path->MakeAbsolute(c->ass->Properties.audio_file, "?script"));
282
set_field(L, "video_file", config::path->MakeAbsolute(c->ass->Properties.video_file, "?script"));
283
set_field(L, "timecodes_file", config::path->MakeAbsolute(c->ass->Properties.timecodes_file, "?script"));
284
set_field(L, "keyframes_file", config::path->MakeAbsolute(c->ass->Properties.keyframes_file, "?script"));
294
void RegisterFeature();
295
void UnregisterFeature();
297
void GetFeatureFunction(const char *function) const;
299
LuaFeature(lua_State *L) : L(L) { }
302
/// Run a lua function on a background thread
303
/// @param L Lua state
304
/// @param nargs Number of arguments the function takes
305
/// @param nresults Number of values the function returns
306
/// @param title Title to use for the progress dialog
307
/// @param parent Parent window for the progress dialog
308
/// @param can_open_config Can the function open its own dialogs?
309
/// @throws agi::UserCancelException if the function fails to run to completion (either due to cancelling or errors)
310
void LuaThreadedCall(lua_State *L, int nargs, int nresults, std::string const& title, wxWindow *parent, bool can_open_config);
312
class LuaCommand final : public cmd::Command, private LuaFeature {
313
std::string cmd_name;
319
LuaCommand(lua_State *L);
322
const char* name() const override { return cmd_name.c_str(); }
323
wxString StrMenu(const agi::Context *) const override { return display; }
324
wxString StrDisplay(const agi::Context *) const override { return display; }
325
wxString StrHelp() const override { return help; }
327
int Type() const override { return cmd_type; }
329
void operator()(agi::Context *c) override;
330
bool Validate(const agi::Context *c) override;
331
virtual bool IsActive(const agi::Context *c) override;
333
static int LuaRegister(lua_State *L);
336
class LuaExportFilter final : public ExportFilter, private LuaFeature {
338
LuaDialog *config_dialog;
341
std::unique_ptr<ScriptDialog> GenerateConfigDialog(wxWindow *parent, agi::Context *c) override;
344
LuaExportFilter(lua_State *L);
345
static int LuaRegister(lua_State *L);
347
void ProcessSubs(AssFile *subs, wxWindow *export_dialog) override;
349
class LuaScript final : public Script {
350
lua_State *L = nullptr;
304
352
std::string name;
305
353
std::string description;
306
354
std::string author;
356
// create lua environment
358
LuaStackcheck _stackcheck(L);
360
// register standard libs
361
push_value(L, luaopen_base); lua_call(L, 0, 0);
362
push_value(L, luaopen_io); lua_call(L, 0, 0);
363
push_value(L, luaopen_lpeg); lua_call(L, 0, 0);
364
push_value(L, luaopen_math); lua_call(L, 0, 0);
365
push_value(L, luaopen_os); lua_call(L, 0, 0);
366
push_value(L, luaopen_package); lua_call(L, 0, 0);
367
push_value(L, luaopen_string); lua_call(L, 0, 0);
368
push_value(L, luaopen_table); lua_call(L, 0, 0);
369
_stackcheck.check_stack(0);
371
// dofile and loadfile are replaced with include
373
lua_setglobal(L, "dofile");
375
lua_setglobal(L, "loadfile");
376
push_value(L, LuaInclude);
377
lua_setglobal(L, "include");
379
// replace pairs and ipairs with lua 5.2-style versions
380
push_value(L, &pairs<table_pairs>);
381
lua_setglobal(L, "pairs");
382
push_value(L, &pairs<table_ipairs>);
383
lua_setglobal(L, "ipairs");
385
// set the module load path to include_path
386
lua_getglobal(L, "package");
387
push_value(L, "path");
391
push_value(L, "path");
395
for (auto const& path : include_path) {
396
lua_pushfstring(L, ";%s/?.lua;%s/?/init.lua", path.string().c_str(), path.string().c_str());
402
// Replace the default lua module loader with our unicode compatible one
403
lua_getfield(L, -1, "loaders");
404
push_value(L, LuaModuleLoader);
405
lua_rawseti(L, -2, 2);
407
_stackcheck.check_stack(0);
409
// prepare stuff in the registry
411
// store the script's filename
412
push_value(L, GetFilename().stem());
413
lua_setfield(L, LUA_REGISTRYINDEX, "filename");
414
_stackcheck.check_stack(0);
416
// reference to the script object
418
lua_setfield(L, LUA_REGISTRYINDEX, "aegisub");
419
_stackcheck.check_stack(0);
421
// make "aegisub" table
422
lua_pushstring(L, "aegisub");
425
set_field(L, "register_macro", LuaCommand::LuaRegister);
426
set_field(L, "register_filter", LuaExportFilter::LuaRegister);
427
set_field(L, "text_extents", lua_text_textents);
428
set_field(L, "frame_from_ms", frame_from_ms);
429
set_field(L, "ms_from_frame", ms_from_frame);
430
set_field(L, "video_size", video_size);
431
set_field(L, "keyframes", get_keyframes);
432
set_field(L, "decode_path", decode_path);
433
set_field(L, "cancel", cancel_script);
434
set_field(L, "lua_automation_version", 4);
435
set_field(L, "__init_regex", regex_init);
436
set_field(L, "__init_clipboard", clipboard_init);
437
set_field(L, "file_name", get_file_name);
438
set_field(L, "gettext", get_translation);
440
// store aegisub table to globals
441
lua_settable(L, LUA_GLOBALSINDEX);
442
_stackcheck.check_stack(0);
445
if (!LoadFile(L, GetFilename())) {
446
std::string err = get_string_or_default(L, 1);
448
throw ScriptLoadError(err);
450
_stackcheck.check_stack(1);
453
// this is where features are registered
454
// don't thread this, as there's no point in it and it seems to break on wx 2.8.3, for some reason
455
if (lua_pcall(L, 0, 0, 0)) {
456
// error occurred, assumed to be on top of Lua stack
457
std::string err = str(boost::format("Error initialising Lua script \"%s\":\n\n%s") % GetPrettyFilename().string() % get_string_or_default(L, -1));
459
throw ScriptLoadError(err);
461
_stackcheck.check_stack(0);
463
lua_getglobal(L, "version");
464
if (lua_isnumber(L, -1) && lua_tointeger(L, -1) == 3) {
465
lua_pop(L, 1); // just to avoid tripping the stackcheck in debug
466
throw ScriptLoadError("Attempted to load an Automation 3 script as an Automation 4 Lua script. Automation 3 is no longer supported.");
469
name = get_global_string(L, "script_name");
470
description = get_global_string(L, "script_description");
471
author = get_global_string(L, "script_author");
472
version = get_global_string(L, "script_version");
475
name = GetPrettyFilename().string();
478
// if we got this far, the script should be ready
479
_stackcheck.check_stack(0);
482
catch (agi::Exception const& e) {
400
name = GetPrettyFilename().string();
402
// create lua environment
407
BOOST_SCOPE_EXIT_ALL(&) { if (!loaded) Destroy(); };
408
LuaStackcheck stackcheck(L);
410
// register standard libs
412
stackcheck.check_stack(0);
414
// dofile and loadfile are replaced with include
416
lua_setglobal(L, "dofile");
418
lua_setglobal(L, "loadfile");
419
push_value(L, exception_wrapper<LuaInclude>);
420
lua_setglobal(L, "include");
422
// Replace the default lua module loader with our unicode compatible
423
// one and set the module search path
424
if (!Install(L, include_path)) {
425
description = get_string_or_default(L, 1);
429
stackcheck.check_stack(0);
431
// prepare stuff in the registry
433
// store the script's filename
434
push_value(L, GetFilename().stem());
435
lua_setfield(L, LUA_REGISTRYINDEX, "filename");
436
stackcheck.check_stack(0);
438
// reference to the script object
440
lua_setfield(L, LUA_REGISTRYINDEX, "aegisub");
441
stackcheck.check_stack(0);
443
// make "aegisub" table
444
lua_pushstring(L, "aegisub");
445
lua_createtable(L, 0, 13);
447
set_field<LuaCommand::LuaRegister>(L, "register_macro");
448
set_field<LuaExportFilter::LuaRegister>(L, "register_filter");
449
set_field<lua_text_textents>(L, "text_extents");
450
set_field<frame_from_ms>(L, "frame_from_ms");
451
set_field<ms_from_frame>(L, "ms_from_frame");
452
set_field<video_size>(L, "video_size");
453
set_field<get_keyframes>(L, "keyframes");
454
set_field<decode_path>(L, "decode_path");
455
set_field<cancel_script>(L, "cancel");
456
set_field(L, "lua_automation_version", 4);
457
set_field<clipboard_init>(L, "__init_clipboard");
458
set_field<get_file_name>(L, "file_name");
459
set_field<get_translation>(L, "gettext");
460
set_field<project_properties>(L, "project_properties");
462
// store aegisub table to globals
463
lua_settable(L, LUA_GLOBALSINDEX);
464
stackcheck.check_stack(0);
467
if (!LoadFile(L, GetFilename())) {
468
description = get_string_or_default(L, 1);
472
stackcheck.check_stack(1);
475
// this is where features are registered
476
// don't thread this, as there's no point in it and it seems to break on wx 2.8.3, for some reason
477
if (lua_pcall(L, 0, 0, 0)) {
478
// error occurred, assumed to be on top of Lua stack
479
description = agi::format("Error initialising Lua script \"%s\":\n\n%s", GetPrettyFilename().string(), get_string_or_default(L, -1));
483
stackcheck.check_stack(0);
485
lua_getglobal(L, "version");
486
if (lua_isnumber(L, -1) && lua_tointeger(L, -1) == 3) {
487
lua_pop(L, 1); // just to avoid tripping the stackcheck in debug
488
description = "Attempted to load an Automation 3 script as an Automation 4 Lua script. Automation 3 is no longer supported.";
492
name = get_global_string(L, "script_name");
493
description = get_global_string(L, "script_description");
494
author = get_global_string(L, "script_author");
495
version = get_global_string(L, "script_version");
484
498
name = GetPrettyFilename().string();
485
description = e.GetChainedMessage();
501
// if we got this far, the script should be ready
489
505
void LuaScript::Destroy()
540
555
return (LuaScript*)ptr;
543
/// @brief Module loader which uses our include rather than Lua's, for unicode file support
544
/// @param L The Lua state
545
/// @return Always 1 per loader_Lua?
546
int LuaScript::LuaModuleLoader(lua_State *L)
548
int pretop = lua_gettop(L);
549
std::string module(luaL_checkstring(L, -1));
550
boost::replace_all(module, ".", LUA_DIRSEP);
552
// Get the lua package include path (which the user may have modified)
553
lua_getglobal(L, "package");
554
lua_getfield(L, -1, "path");
555
std::string package_paths(luaL_checkstring(L, -1));
558
boost::char_separator<char> sep(";");
559
for (auto filename : boost::tokenizer<boost::char_separator<char>>(package_paths, sep)) {
560
boost::replace_all(filename, "?", module);
562
// If there's a .moon file at that path, load it instead of the
564
agi::fs::path path = filename;
565
if (agi::fs::HasExtension(path, "lua")) {
566
agi::fs::path moonpath = path;
567
moonpath.replace_extension("moon");
568
if (agi::fs::FileExists(moonpath))
573
if (!LoadFile(L, path))
574
return luaL_error(L, "Error loading Lua module \"%s\":\n%s", path.string().c_str(), luaL_checkstring(L, 1));
577
catch (agi::fs::FileNotFound const&) {
578
// Not an error so swallow and continue on
580
catch (agi::fs::NotAFile const&) {
581
// Not an error so swallow and continue on
583
catch (agi::Exception const& e) {
584
return luaL_error(L, "Error loading Lua module \"%s\":\n%s", path.string().c_str(), e.GetChainedMessage().c_str());
588
return lua_gettop(L) - pretop;
591
559
int LuaScript::LuaInclude(lua_State *L)
593
561
const LuaScript *s = GetScriptObject(L);
595
const std::string filename(luaL_checkstring(L, 1));
563
const std::string filename(check_string(L, 1));
596
564
agi::fs::path filepath;
598
566
// Relative or absolute path
609
577
if (!agi::fs::FileExists(filepath))
610
return luaL_error(L, "Lua include not found: %s", filename.c_str());
578
return error(L, "Lua include not found: %s", filename.c_str());
612
580
if (!LoadFile(L, filepath))
613
return luaL_error(L, "Error loading Lua include \"%s\":\n%s", filename.c_str(), luaL_checkstring(L, 1));
581
return error(L, "Error loading Lua include \"%s\":\n%s", filename.c_str(), check_string(L, 1).c_str());
615
583
int pretop = lua_gettop(L) - 1; // don't count the function value itself
616
584
lua_call(L, 0, LUA_MULTRET);
617
585
return lua_gettop(L) - pretop;
620
static int moon_line(lua_State *L, int lua_line, std::string const& file)
622
if (luaL_dostring(L, "return require 'moonscript.line_tables'")) {
623
lua_pop(L, 1); // pop error message
630
if (!lua_istable(L, -1)) {
635
lua_rawgeti(L, -1, lua_line);
636
if (!lua_isnumber(L, -1)) {
641
auto char_pos = static_cast<size_t>(lua_tonumber(L, -1));
644
// The moonscript line tables give us a character offset into the file,
645
// so now we need to map that to a line number
646
lua_getfield(L, LUA_REGISTRYINDEX, ("raw moonscript: " + file).c_str());
647
if (!lua_isstring(L, -1)) {
653
auto moon = lua_tolstring(L, -1, &moon_len);
654
return std::count(moon, moon + std::min(moon_len, char_pos), '\n') + 1;
657
static int add_stack_trace(lua_State *L)
660
if (lua_isnumber(L, 2)) {
661
level = (int)lua_tointeger(L, 2);
665
const char *err = lua_tostring(L, 1);
668
std::string message = err;
672
// Strip the location from the error message since it's redundant with
674
boost::regex location(R"(^\[string ".*"\]:[0-9]+: )");
675
message = regex_replace(message, location, "", boost::format_first_only);
677
std::vector<std::string> frames;
678
frames.emplace_back(std::move(message));
681
while (lua_getstack(L, level++, &ar)) {
682
lua_getinfo(L, "Snl", &ar);
684
if (ar.what[0] == 't')
685
frames.emplace_back("(tail call)");
687
bool is_moon = false;
688
std::string file = ar.source;
690
file = "<C function>";
691
else if (boost::ends_with(file, ".moon"))
694
auto real_line = [&](int line) {
695
return is_moon ? moon_line(L, line, file) : line;
698
std::string function = ar.name ? ar.name : "";
701
else if (*ar.what == 'C')
703
else if (!*ar.namewhat)
704
function = str(boost::format("<anonymous function at lines %d-%d>") % real_line(ar.linedefined) % real_line(ar.lastlinedefined - 1));
706
frames.emplace_back(str(boost::format(" File \"%s\", line %d\n%s") % file % real_line(ar.currentline) % function));
710
push_value(L, join(frames | boost::adaptors::reversed, "\n"));
715
588
void LuaThreadedCall(lua_State *L, int nargs, int nresults, std::string const& title, wxWindow *parent, bool can_open_config)
717
590
bool failed = false;