~ubuntu-branches/ubuntu/wily/aegisub/wily-proposed

« back to all changes in this revision

Viewing changes to src/auto4_lua.cpp

  • Committer: Package Import Robot
  • Author(s): Sebastian Reichel, Pascal De Vuyst, Juan Picca, Sebastian Reichel
  • Date: 2015-08-04 21:40:50 UTC
  • mfrom: (5.1.1 sid)
  • Revision ID: package-import@ubuntu.com-20150804214050-y2aghm9vdksoc8t7
Tags: 3.2.2+dfsg-1
[ Pascal De Vuyst ]
* Fix Typo in package description (Closes: #739219)

[ Juan Picca ]
* Add patch to fix reproducible build (Closes: #789728)

[ Sebastian Reichel ]
* New upstream release
 - remove vendor directory from orig tarball
* Update Debian Standards Version to 3.9.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
32
32
/// @ingroup scripting
33
33
///
34
34
 
35
 
#include "config.h"
36
 
 
37
35
#include "auto4_lua.h"
38
36
 
39
 
#include "auto4_lua_utils.h"
40
37
#include "ass_dialogue.h"
41
38
#include "ass_file.h"
 
39
#include "ass_info.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"
47
 
#include "main.h"
48
46
#include "options.h"
 
47
#include "project.h"
49
48
#include "selection_controller.h"
50
49
#include "subs_controller.h"
51
 
#include "video_context.h"
 
50
#include "video_controller.h"
52
51
#include "utils.h"
53
52
 
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>
57
59
 
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>
67
65
#include <cassert>
68
 
#include <cstdint>
69
66
#include <mutex>
70
 
 
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>
 
70
 
 
71
using namespace agi::lua;
 
72
using namespace Automation4;
77
73
 
78
74
namespace {
 
75
        wxString get_wxstring(lua_State *L, int idx)
 
76
        {
 
77
                return wxString::FromUTF8(lua_tostring(L, idx));
 
78
        }
 
79
 
 
80
        wxString check_wxstring(lua_State *L, int idx)
 
81
        {
 
82
                return to_wx(check_string(L, idx));
 
83
        }
 
84
 
79
85
        void set_context(lua_State *L, const agi::Context *c)
80
86
        {
81
87
                // Explicit cast is needed to discard the const
108
114
        int get_translation(lua_State *L)
109
115
        {
110
116
                wxString str(check_wxstring(L, 1));
111
 
                push_value(L, _(str));
 
117
                push_value(L, _(str).utf8_str());
112
118
                return 1;
113
119
        }
114
120
 
124
130
 
125
131
        int clipboard_set(lua_State *L)
126
132
        {
127
 
                std::string str(luaL_checkstring(L, 1));
 
133
                std::string str(check_string(L, 1));
128
134
 
129
135
                bool succeeded = false;
130
136
 
147
153
 
148
154
        int clipboard_init(lua_State *L)
149
155
        {
150
 
                lua_newtable(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");
153
159
                return 1;
154
160
        }
155
161
 
156
 
        struct table_pairs {
157
 
                static void init(lua_State *L) { lua_pushnil(L); }
158
 
                static int next(lua_State *L) {
159
 
                        luaL_checktype(L, 1, LUA_TTABLE);
160
 
                        lua_settop(L, 2);
161
 
                        if (lua_next(L, 1))
162
 
                                return 2;
163
 
                        lua_pushnil(L);
164
 
                        return 1;
165
 
                }
166
 
                static const char *method() { return "__pairs"; }
167
 
        };
168
 
 
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;
177
 
                }
178
 
                static const char *method() { return "__ipairs"; }
179
 
        };
180
 
 
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())) {
185
 
                        lua_pushvalue(L, 1);
186
 
                        lua_call(L, 1, 3);
187
 
                        return 3;
188
 
                }
189
 
 
190
 
                // No metamethod, so use the table iterators
191
 
                luaL_checktype(L, 1, LUA_TTABLE);
192
 
                lua_pushcfunction(L, &TableIter::next);
193
 
                lua_pushvalue(L, 1);
194
 
                TableIter::init(L);
195
 
                return 3;
196
 
        }
197
 
 
198
162
        int frame_from_ms(lua_State *L)
199
163
        {
200
164
                const agi::Context *c = get_context(L);
201
165
                int ms = lua_tointeger(L, -1);
202
166
                lua_pop(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));
205
169
                else
206
170
                        lua_pushnil(L);
213
177
                const agi::Context *c = get_context(L);
214
178
                int frame = lua_tointeger(L, -1);
215
179
                lua_pop(L, 1);
216
 
                if (c && c->videoController->TimecodesLoaded())
 
180
                if (c && c->project->Timecodes().IsLoaded())
217
181
                        push_value(L, c->videoController->TimeAtFrame(frame, agi::vfr::START));
218
182
                else
219
183
                        lua_pushnil(L);
223
187
        int video_size(lua_State *L)
224
188
        {
225
189
                const agi::Context *c = get_context(L);
226
 
                if (c && c->videoController->IsLoaded()) {
227
 
                        push_value(L, c->videoController->GetWidth());
228
 
                        push_value(L, c->videoController->GetHeight());
 
190
                if (c && c->project->VideoProvider()) {
 
191
                        auto provider = c->project->VideoProvider();
 
192
                        push_value(L, provider->GetWidth());
 
193
                        push_value(L, provider->GetHeight());
229
194
                        push_value(L, c->videoController->GetAspectRatioValue());
230
195
                        push_value(L, (int)c->videoController->GetAspectRatioType());
231
196
                        return 4;
239
204
        int get_keyframes(lua_State *L)
240
205
        {
241
206
                const agi::Context *c = get_context(L);
242
 
                if (!c) {
 
207
                if (c)
 
208
                        push_value(L, c->project->Keyframes());
 
209
                else
243
210
                        lua_pushnil(L);
244
 
                        return 1;
245
 
                }
246
 
 
247
 
                std::vector<int> const& kf = c->videoController->GetKeyFrames();
248
 
 
249
 
                lua_newtable(L);
250
 
                for (size_t i = 0; i < kf.size(); ++i) {
251
 
                        push_value(L, kf[i]);
252
 
                        lua_rawseti(L, -2, i);
253
 
                }
254
 
 
255
211
                return 1;
256
212
        }
257
213
 
258
214
        int decode_path(lua_State *L)
259
215
        {
260
 
                std::string path = luaL_checkstring(L, 1);
 
216
                std::string path = check_string(L, 1);
261
217
                lua_pop(L, 1);
262
218
                push_value(L, config::path->Decode(path));
263
219
                return 1;
266
222
        int cancel_script(lua_State *L)
267
223
        {
268
224
                lua_pushnil(L);
269
 
                return lua_error(L);
 
225
                throw error_tag();
270
226
        }
271
227
 
272
228
        int lua_text_textents(lua_State *L)
273
229
        {
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, "");
 
232
 
 
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
 
235
                {
 
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");
 
241
                        lua_pop(L, 1);
 
242
                }
276
243
 
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());
280
247
                lua_pop(L, 1);
281
248
                if (!st)
282
 
                        return luaL_error(L, "Not a style entry");
 
249
                        return error(L, "Not a style entry");
283
250
 
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");
287
254
 
288
255
                push_value(L, width);
289
256
                push_value(L, height);
291
258
                push_value(L, extlead);
292
259
                return 4;
293
260
        }
294
 
}
295
 
 
296
 
extern "C" int luaopen_lpeg (lua_State *L);
297
 
 
298
 
namespace Automation4 {
299
 
        int regex_init(lua_State *L);
300
 
 
301
 
        class LuaScript : public Script {
 
261
 
 
262
        int project_properties(lua_State *L)
 
263
        {
 
264
                const agi::Context *c = get_context(L);
 
265
                if (!c)
 
266
                        lua_pushnil(L);
 
267
                else {
 
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);
 
278
                        PUSH_FIELD(ar_mode);
 
279
                        PUSH_FIELD(video_position);
 
280
#undef PUSH_FIELD
 
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"));
 
285
                }
 
286
                return 1;
 
287
        }
 
288
 
 
289
        class LuaFeature {
 
290
                int myid = 0;
 
291
        protected:
302
292
                lua_State *L;
303
293
 
 
294
                void RegisterFeature();
 
295
                void UnregisterFeature();
 
296
 
 
297
                void GetFeatureFunction(const char *function) const;
 
298
 
 
299
                LuaFeature(lua_State *L) : L(L) { }
 
300
        };
 
301
 
 
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);
 
311
 
 
312
        class LuaCommand final : public cmd::Command, private LuaFeature {
 
313
                std::string cmd_name;
 
314
                wxString display;
 
315
                wxString help;
 
316
                int cmd_type;
 
317
 
 
318
        public:
 
319
                LuaCommand(lua_State *L);
 
320
                ~LuaCommand();
 
321
 
 
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; }
 
326
 
 
327
                int Type() const override { return cmd_type; }
 
328
 
 
329
                void operator()(agi::Context *c) override;
 
330
                bool Validate(const agi::Context *c) override;
 
331
                virtual bool IsActive(const agi::Context *c) override;
 
332
 
 
333
                static int LuaRegister(lua_State *L);
 
334
        };
 
335
 
 
336
        class LuaExportFilter final : public ExportFilter, private LuaFeature {
 
337
                bool has_config;
 
338
                LuaDialog *config_dialog;
 
339
 
 
340
        protected:
 
341
                std::unique_ptr<ScriptDialog> GenerateConfigDialog(wxWindow *parent, agi::Context *c) override;
 
342
 
 
343
        public:
 
344
                LuaExportFilter(lua_State *L);
 
345
                static int LuaRegister(lua_State *L);
 
346
 
 
347
                void ProcessSubs(AssFile *subs, wxWindow *export_dialog) override;
 
348
        };
 
349
        class LuaScript final : public Script {
 
350
                lua_State *L = nullptr;
 
351
 
304
352
                std::string name;
305
353
                std::string description;
306
354
                std::string author;
315
363
                void Destroy();
316
364
 
317
365
                static int LuaInclude(lua_State *L);
318
 
                static int LuaModuleLoader(lua_State *L);
319
366
 
320
367
        public:
321
368
                LuaScript(agi::fs::path const& filename);
338
385
 
339
386
                std::vector<cmd::Command*> GetMacros() const override { return macros; }
340
387
                std::vector<ExportFilter*> GetFilters() const override;
341
 
                std::vector<SubtitleFormat*> GetFormats() const override { return {}; }
342
388
        };
343
389
 
344
390
        LuaScript::LuaScript(agi::fs::path const& filename)
345
391
        : Script(filename)
346
 
        , L(nullptr)
347
392
        {
348
393
                Create();
349
394
        }
352
397
        {
353
398
                Destroy();
354
399
 
355
 
                try {
356
 
                        // create lua environment
357
 
                        L = lua_open();
358
 
                        LuaStackcheck _stackcheck(L);
359
 
 
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);
370
 
 
371
 
                        // dofile and loadfile are replaced with include
372
 
                        lua_pushnil(L);
373
 
                        lua_setglobal(L, "dofile");
374
 
                        lua_pushnil(L);
375
 
                        lua_setglobal(L, "loadfile");
376
 
                        push_value(L, LuaInclude);
377
 
                        lua_setglobal(L, "include");
378
 
 
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");
384
 
 
385
 
                        // set the module load path to include_path
386
 
                        lua_getglobal(L, "package");
387
 
                        push_value(L, "path");
388
 
#ifdef __WXMSW__
389
 
                        push_value(L, "");
390
 
#else
391
 
                        push_value(L, "path");
392
 
                        lua_gettable(L, -3);
393
 
#endif
394
 
 
395
 
                        for (auto const& path : include_path) {
396
 
                                lua_pushfstring(L, ";%s/?.lua;%s/?/init.lua", path.string().c_str(), path.string().c_str());
397
 
                                lua_concat(L, 2);
398
 
                        }
399
 
 
400
 
                        lua_settable(L, -3);
401
 
 
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);
406
 
                        lua_pop(L, 2);
407
 
                        _stackcheck.check_stack(0);
408
 
 
409
 
                        // prepare stuff in the registry
410
 
 
411
 
                        // store the script's filename
412
 
                        push_value(L, GetFilename().stem());
413
 
                        lua_setfield(L, LUA_REGISTRYINDEX, "filename");
414
 
                        _stackcheck.check_stack(0);
415
 
 
416
 
                        // reference to the script object
417
 
                        push_value(L, this);
418
 
                        lua_setfield(L, LUA_REGISTRYINDEX, "aegisub");
419
 
                        _stackcheck.check_stack(0);
420
 
 
421
 
                        // make "aegisub" table
422
 
                        lua_pushstring(L, "aegisub");
423
 
                        lua_newtable(L);
424
 
 
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);
439
 
 
440
 
                        // store aegisub table to globals
441
 
                        lua_settable(L, LUA_GLOBALSINDEX);
442
 
                        _stackcheck.check_stack(0);
443
 
 
444
 
                        // load user script
445
 
                        if (!LoadFile(L, GetFilename())) {
446
 
                                std::string err = get_string_or_default(L, 1);
447
 
                                lua_pop(L, 1);
448
 
                                throw ScriptLoadError(err);
449
 
                        }
450
 
                        _stackcheck.check_stack(1);
451
 
 
452
 
                        // and execute it
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));
458
 
                                lua_pop(L, 1);
459
 
                                throw ScriptLoadError(err);
460
 
                        }
461
 
                        _stackcheck.check_stack(0);
462
 
 
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.");
467
 
                        }
468
 
 
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");
473
 
 
474
 
                        if (name.empty())
475
 
                                name = GetPrettyFilename().string();
476
 
 
477
 
                        lua_pop(L, 1);
478
 
                        // if we got this far, the script should be ready
479
 
                        _stackcheck.check_stack(0);
480
 
 
481
 
                }
482
 
                catch (agi::Exception const& e) {
483
 
                        Destroy();
 
400
                name = GetPrettyFilename().string();
 
401
 
 
402
                // create lua environment
 
403
                L = lua_open();
 
404
                if (!L) return;
 
405
 
 
406
                bool loaded = false;
 
407
                BOOST_SCOPE_EXIT_ALL(&) { if (!loaded) Destroy(); };
 
408
                LuaStackcheck stackcheck(L);
 
409
 
 
410
                // register standard libs
 
411
                preload_modules(L);
 
412
                stackcheck.check_stack(0);
 
413
 
 
414
                // dofile and loadfile are replaced with include
 
415
                lua_pushnil(L);
 
416
                lua_setglobal(L, "dofile");
 
417
                lua_pushnil(L);
 
418
                lua_setglobal(L, "loadfile");
 
419
                push_value(L, exception_wrapper<LuaInclude>);
 
420
                lua_setglobal(L, "include");
 
421
 
 
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);
 
426
                        lua_pop(L, 1);
 
427
                        return;
 
428
                }
 
429
                stackcheck.check_stack(0);
 
430
 
 
431
                // prepare stuff in the registry
 
432
 
 
433
                // store the script's filename
 
434
                push_value(L, GetFilename().stem());
 
435
                lua_setfield(L, LUA_REGISTRYINDEX, "filename");
 
436
                stackcheck.check_stack(0);
 
437
 
 
438
                // reference to the script object
 
439
                push_value(L, this);
 
440
                lua_setfield(L, LUA_REGISTRYINDEX, "aegisub");
 
441
                stackcheck.check_stack(0);
 
442
 
 
443
                // make "aegisub" table
 
444
                lua_pushstring(L, "aegisub");
 
445
                lua_createtable(L, 0, 13);
 
446
 
 
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");
 
461
 
 
462
                // store aegisub table to globals
 
463
                lua_settable(L, LUA_GLOBALSINDEX);
 
464
                stackcheck.check_stack(0);
 
465
 
 
466
                // load user script
 
467
                if (!LoadFile(L, GetFilename())) {
 
468
                        description = get_string_or_default(L, 1);
 
469
                        lua_pop(L, 1);
 
470
                        return;
 
471
                }
 
472
                stackcheck.check_stack(1);
 
473
 
 
474
                // and execute it
 
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));
 
480
                        lua_pop(L, 1);
 
481
                        return;
 
482
                }
 
483
                stackcheck.check_stack(0);
 
484
 
 
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.";
 
489
                        return;
 
490
                }
 
491
 
 
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");
 
496
 
 
497
                if (name.empty())
484
498
                        name = GetPrettyFilename().string();
485
 
                        description = e.GetChainedMessage();
486
 
                }
 
499
 
 
500
                lua_pop(L, 1);
 
501
                // if we got this far, the script should be ready
 
502
                loaded = true;
487
503
        }
488
504
 
489
505
        void LuaScript::Destroy()
514
530
        {
515
531
                for (auto macro : macros) {
516
532
                        if (macro->name() == command->name()) {
517
 
                                luaL_error(L,
518
 
                                        "A macro named '%s' is already defined in script '%s'",
 
533
                                error(L, "A macro named '%s' is already defined in script '%s'",
519
534
                                        command->StrDisplay(nullptr).utf8_str().data(), name.c_str());
520
535
                        }
521
536
                }
540
555
                return (LuaScript*)ptr;
541
556
        }
542
557
 
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)
547
 
        {
548
 
                int pretop = lua_gettop(L);
549
 
                std::string module(luaL_checkstring(L, -1));
550
 
                boost::replace_all(module, ".", LUA_DIRSEP);
551
 
 
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));
556
 
                lua_pop(L, 2);
557
 
 
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);
561
 
 
562
 
                        // If there's a .moon file at that path, load it instead of the
563
 
                        // .lua file
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))
569
 
                                        path = moonpath;
570
 
                        }
571
 
 
572
 
                        try {
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));
575
 
                                break;
576
 
                        }
577
 
                        catch (agi::fs::FileNotFound const&) {
578
 
                                // Not an error so swallow and continue on
579
 
                        }
580
 
                        catch (agi::fs::NotAFile const&) {
581
 
                                // Not an error so swallow and continue on
582
 
                        }
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());
585
 
                        }
586
 
                }
587
 
 
588
 
                return lua_gettop(L) - pretop;
589
 
        }
590
558
 
591
559
        int LuaScript::LuaInclude(lua_State *L)
592
560
        {
593
561
                const LuaScript *s = GetScriptObject(L);
594
562
 
595
 
                const std::string filename(luaL_checkstring(L, 1));
 
563
                const std::string filename(check_string(L, 1));
596
564
                agi::fs::path filepath;
597
565
 
598
566
                // Relative or absolute path
607
575
                }
608
576
 
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());
611
579
 
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());
614
582
 
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;
618
586
        }
619
587
 
620
 
        static int moon_line(lua_State *L, int lua_line, std::string const& file)
621
 
        {
622
 
                if (luaL_dostring(L, "return require 'moonscript.line_tables'")) {
623
 
                        lua_pop(L, 1); // pop error message
624
 
                        return lua_line;
625
 
                }
626
 
 
627
 
                push_value(L, file);
628
 
                lua_rawget(L, -2);
629
 
 
630
 
                if (!lua_istable(L, -1)) {
631
 
                        lua_pop(L, 2);
632
 
                        return lua_line;
633
 
                }
634
 
 
635
 
                lua_rawgeti(L, -1, lua_line);
636
 
                if (!lua_isnumber(L, -1)) {
637
 
                        lua_pop(L, 3);
638
 
                        return lua_line;
639
 
                }
640
 
 
641
 
                auto char_pos = static_cast<size_t>(lua_tonumber(L, -1));
642
 
                lua_pop(L, 3);
643
 
 
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)) {
648
 
                        lua_pop(L, 1);
649
 
                        return lua_line;
650
 
                }
651
 
 
652
 
                size_t moon_len;
653
 
                auto moon = lua_tolstring(L, -1, &moon_len);
654
 
                return std::count(moon, moon + std::min(moon_len, char_pos), '\n') + 1;
655
 
        }
656
 
 
657
 
        static int add_stack_trace(lua_State *L)
658
 
        {
659
 
                int level = 1;
660
 
                if (lua_isnumber(L, 2)) {
661
 
                        level = (int)lua_tointeger(L, 2);
662
 
                        lua_pop(L, 1);
663
 
                }
664
 
 
665
 
                const char *err = lua_tostring(L, 1);
666
 
                if (!err) return 1;
667
 
 
668
 
                std::string message = err;
669
 
                if (lua_gettop(L))
670
 
                        lua_pop(L, 1);
671
 
 
672
 
                // Strip the location from the error message since it's redundant with
673
 
                // the stack trace
674
 
                boost::regex location(R"(^\[string ".*"\]:[0-9]+: )");
675
 
                message = regex_replace(message, location, "", boost::format_first_only);
676
 
 
677
 
                std::vector<std::string> frames;
678
 
                frames.emplace_back(std::move(message));
679
 
 
680
 
                lua_Debug ar;
681
 
                while (lua_getstack(L, level++, &ar)) {
682
 
                        lua_getinfo(L, "Snl", &ar);
683
 
 
684
 
                        if (ar.what[0] == 't')
685
 
                                frames.emplace_back("(tail call)");
686
 
                        else {
687
 
                                bool is_moon = false;
688
 
                                std::string file = ar.source;
689
 
                                if (file == "=[C]")
690
 
                                        file = "<C function>";
691
 
                                else if (boost::ends_with(file, ".moon"))
692
 
                                        is_moon = true;
693
 
 
694
 
                                auto real_line = [&](int line) {
695
 
                                        return is_moon ? moon_line(L, line, file) : line;
696
 
                                };
697
 
 
698
 
                                std::string function = ar.name ? ar.name : "";
699
 
                                if (*ar.what == 'm')
700
 
                                        function = "<main>";
701
 
                                else if (*ar.what == 'C')
702
 
                                        function = '?';
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));
705
 
 
706
 
                                frames.emplace_back(str(boost::format("    File \"%s\", line %d\n%s") % file % real_line(ar.currentline) % function));
707
 
                        }
708
 
                }
709
 
 
710
 
                push_value(L, join(frames | boost::adaptors::reversed, "\n"));
711
 
 
712
 
                return 1;
713
 
        }
714
 
 
715
588
        void LuaThreadedCall(lua_State *L, int nargs, int nresults, std::string const& title, wxWindow *parent, bool can_open_config)
716
589
        {
717
590
                bool failed = false;
742
615
        }
743
616
 
744
617
        // LuaFeature
745
 
        LuaFeature::LuaFeature(lua_State *L)
746
 
        : myid(0)
747
 
        , L(L)
748
 
        {
749
 
        }
750
 
 
751
618
        void LuaFeature::RegisterFeature()
752
619
        {
753
620
                myid = luaL_ref(L, LUA_REGISTRYINDEX);
774
641
        int LuaCommand::LuaRegister(lua_State *L)
775
642
        {
776
643
                static std::mutex mutex;
777
 
                auto command = agi::util::make_unique<LuaCommand>(L);
 
644
                auto command = agi::make_unique<LuaCommand>(L);
778
645
                {
779
646
                        std::lock_guard<std::mutex> lock(mutex);
780
647
                        cmd::reg(std::move(command));
789
656
        , cmd_type(cmd::COMMAND_NORMAL)
790
657
        {
791
658
                lua_getfield(L, LUA_REGISTRYINDEX, "filename");
792
 
                cmd_name = str(boost::format("automation/lua/%s/%s") % luaL_checkstring(L, -1) % luaL_checkstring(L, 1));
 
659
                cmd_name = agi::format("automation/lua/%s/%s", check_string(L, -1), check_string(L, 1));
793
660
 
794
661
                if (!lua_isfunction(L, 3))
795
 
                        luaL_error(L, "The macro processing function must be a function");
 
662
                        error(L, "The macro processing function must be a function");
796
663
 
797
664
                if (lua_isfunction(L, 4))
798
665
                        cmd_type |= cmd::COMMAND_VALIDATE;
801
668
                        cmd_type |= cmd::COMMAND_TOGGLE;
802
669
 
803
670
                // new table for containing the functions for this feature
804
 
                lua_newtable(L);
 
671
                lua_createtable(L, 0, 3);
805
672
 
806
673
                // store processing function
807
674
                push_value(L, "run");
830
697
                LuaScript::GetScriptObject(L)->UnregisterCommand(this);
831
698
        }
832
699
 
833
 
        static int transform_selection(lua_State *L, const agi::Context *c)
 
700
        static std::vector<int> selected_rows(const agi::Context *c)
834
701
        {
835
 
                SubtitleSelection const& sel = c->selectionController->GetSelectedSet();
836
 
                AssDialogue *active_line = c->selectionController->GetActiveLine();
837
 
 
838
 
                lua_newtable(L);
839
 
                int active_idx = -1;
840
 
 
841
 
                int row = 0;
842
 
                int idx = 1;
843
 
                for (auto& line : c->ass->Line) {
844
 
                        ++row;
845
 
                        AssDialogue *diag = dynamic_cast<AssDialogue*>(&line);
846
 
                        if (!diag) continue;
847
 
 
848
 
                        if (diag == active_line) active_idx = row;
849
 
                        if (sel.count(diag)) {
850
 
                                push_value(L, row);
851
 
                                lua_rawseti(L, -2, idx++);
852
 
                        }
853
 
                }
854
 
 
855
 
                return active_idx;
 
702
                auto const& sel = c->selectionController->GetSelectedSet();
 
703
                int offset = c->ass->Info.size() + c->ass->Styles.size();
 
704
                std::vector<int> rows;
 
705
                rows.reserve(sel.size());
 
706
                for (auto line : sel)
 
707
                        rows.push_back(line->Row + offset + 1);
 
708
                sort(begin(rows), end(rows));
 
709
                return rows;
856
710
        }
857
711
 
858
712
        bool LuaCommand::Validate(const agi::Context *c)
862
716
                set_context(L, c);
863
717
 
864
718
                GetFeatureFunction("validate");
865
 
                auto subsobj = new LuaAssFile(L, c->ass);
866
 
                push_value(L, transform_selection(L, c));
 
719
                auto subsobj = new LuaAssFile(L, c->ass.get());
 
720
 
 
721
                push_value(L, selected_rows(c));
 
722
                if (auto active_line = c->selectionController->GetActiveLine())
 
723
                        push_value(L, active_line->Row + c->ass->Info.size() + c->ass->Styles.size() + 1);
867
724
 
868
725
                int err = lua_pcall(L, 3, 2, 0);
869
726
 
895
752
                stackcheck.check_stack(0);
896
753
 
897
754
                GetFeatureFunction("run");
898
 
                auto subsobj = new LuaAssFile(L, c->ass, true, true);
899
 
                push_value(L, transform_selection(L, c));
 
755
                auto subsobj = new LuaAssFile(L, c->ass.get(), true, true);
 
756
 
 
757
                int original_offset = c->ass->Info.size() + c->ass->Styles.size() + 1;
 
758
                auto original_sel = selected_rows(c);
 
759
                int original_active = 0;
 
760
                if (auto active_line = c->selectionController->GetActiveLine())
 
761
                        original_active = active_line->Row + original_offset;
 
762
 
 
763
                push_value(L, original_sel);
 
764
                push_value(L, original_active);
900
765
 
901
766
                try {
902
767
                        LuaThreadedCall(L, 3, 2, from_wx(StrDisplay(c)), c->parent, true);
903
768
 
904
 
                        subsobj->ProcessingComplete(StrDisplay(c));
 
769
                        auto lines = subsobj->ProcessingComplete(StrDisplay(c));
905
770
 
906
771
                        AssDialogue *active_line = nullptr;
907
 
                        int active_idx = 0;
 
772
                        int active_idx = original_active;
908
773
 
909
774
                        // Check for a new active row
910
775
                        if (lua_isnumber(L, -1)) {
911
776
                                active_idx = lua_tointeger(L, -1);
912
 
                                if (active_idx < 1 || active_idx > (int)c->ass->Line.size()) {
913
 
                                        wxLogError("Active row %d is out of bounds (must be 1-%u)", active_idx, c->ass->Line.size());
914
 
                                        active_idx = 0;
 
777
                                if (active_idx < 1 || active_idx > (int)lines.size()) {
 
778
                                        wxLogError("Active row %d is out of bounds (must be 1-%u)", active_idx, lines.size());
 
779
                                        active_idx = original_active;
915
780
                                }
916
781
                        }
917
782
 
921
786
                        // top of stack will be selected lines array, if any was returned
922
787
                        if (lua_istable(L, -1)) {
923
788
                                std::set<AssDialogue*> sel;
924
 
                                entryIter it = c->ass->Line.begin();
925
 
                                int last_idx = 1;
926
789
                                lua_for_each(L, [&] {
927
790
                                        if (lua_isnumber(L, -1)) {
928
791
                                                int cur = lua_tointeger(L, -1);
929
 
                                                if (cur < 1 || cur > (int)c->ass->Line.size()) {
930
 
                                                        wxLogError("Selected row %d is out of bounds (must be 1-%u)", cur, c->ass->Line.size());
 
792
                                                if (cur < 1 || cur > (int)lines.size()) {
 
793
                                                        wxLogError("Selected row %d is out of bounds (must be 1-%u)", cur, lines.size());
931
794
                                                        throw LuaForEachBreak();
932
795
                                                }
933
796
 
934
 
                                                advance(it, cur - last_idx);
935
 
 
936
 
                                                AssDialogue *diag = dynamic_cast<AssDialogue*>(&*it);
 
797
                                                auto diag = dynamic_cast<AssDialogue*>(lines[cur - 1]);
937
798
                                                if (!diag) {
938
799
                                                        wxLogError("Selected row %d is not a dialogue line", cur);
939
800
                                                        throw LuaForEachBreak();
940
801
                                                }
941
802
 
942
803
                                                sel.insert(diag);
943
 
                                                last_idx = cur;
944
804
                                                if (!active_line || active_idx == cur)
945
805
                                                        active_line = diag;
946
806
                                        }
949
809
                                AssDialogue *new_active = c->selectionController->GetActiveLine();
950
810
                                if (active_line && (active_idx > 0 || !sel.count(new_active)))
951
811
                                        new_active = active_line;
952
 
                                c->selectionController->SetSelectionAndActive(sel, new_active);
 
812
                                c->selectionController->SetSelectionAndActive(std::move(sel), new_active);
953
813
                        }
954
 
                        else
 
814
                        else {
955
815
                                lua_pop(L, 1);
956
816
 
 
817
                                Selection new_sel;
 
818
                                AssDialogue *new_active = nullptr;
 
819
 
 
820
                                int prev = original_offset;
 
821
                                auto it = c->ass->Events.begin();
 
822
                                for (int row : original_sel) {
 
823
                                        while (row > prev && it != c->ass->Events.end()) {
 
824
                                                ++prev;
 
825
                                                ++it;
 
826
                                        }
 
827
                                        if (row != prev) break;
 
828
                                        new_sel.insert(&*it);
 
829
                                        if (row == original_active)
 
830
                                                new_active = &*it;
 
831
                                }
 
832
 
 
833
                                if (new_sel.empty() && !c->ass->Events.empty())
 
834
                                        new_sel.insert(&c->ass->Events.front());
 
835
                                if (!new_sel.count(new_active))
 
836
                                        new_active = *new_sel.begin();
 
837
                                c->selectionController->SetSelectionAndActive(std::move(new_sel), new_active);
 
838
                        }
 
839
 
957
840
                        stackcheck.check_stack(0);
958
841
                }
959
842
                catch (agi::UserCancelException const&) {
972
855
                stackcheck.check_stack(0);
973
856
 
974
857
                GetFeatureFunction("isactive");
975
 
                auto subsobj = new LuaAssFile(L, c->ass);
976
 
                push_value(L, transform_selection(L, c));
 
858
                auto subsobj = new LuaAssFile(L, c->ass.get());
 
859
                push_value(L, selected_rows(c));
 
860
                if (auto active_line = c->selectionController->GetActiveLine())
 
861
                        push_value(L, active_line->Row + c->ass->Info.size() + c->ass->Styles.size() + 1);
977
862
 
978
863
                int err = lua_pcall(L, 3, 1, 0);
979
864
                subsobj->ProcessingComplete();
993
878
 
994
879
        // LuaFeatureFilter
995
880
        LuaExportFilter::LuaExportFilter(lua_State *L)
996
 
        : ExportFilter(luaL_checkstring(L, 1), lua_tostring(L, 2), lua_tointeger(L, 3))
 
881
        : ExportFilter(check_string(L, 1), lua_tostring(L, 2), lua_tointeger(L, 3))
997
882
        , LuaFeature(L)
998
883
        {
999
884
                if (!lua_isfunction(L, 4))
1000
 
                        luaL_error(L, "The filter processing function must be a function");
 
885
                        error(L, "The filter processing function must be a function");
1001
886
 
1002
887
                // new table for containing the functions for this feature
1003
 
                lua_newtable(L);
 
888
                lua_createtable(L, 0, 2);
1004
889
 
1005
890
                // store processing function
1006
891
                push_value(L, "run");
1022
907
        int LuaExportFilter::LuaRegister(lua_State *L)
1023
908
        {
1024
909
                static std::mutex mutex;
1025
 
                auto filter = agi::util::make_unique<LuaExportFilter>(L);
 
910
                auto filter = agi::make_unique<LuaExportFilter>(L);
1026
911
                {
1027
912
                        std::lock_guard<std::mutex> lock(mutex);
1028
913
                        AssExportFilterChain::Register(std::move(filter));
1067
952
                }
1068
953
        }
1069
954
 
1070
 
        ScriptDialog* LuaExportFilter::GenerateConfigDialog(wxWindow *parent, agi::Context *c)
 
955
        std::unique_ptr<ScriptDialog> LuaExportFilter::GenerateConfigDialog(wxWindow *parent, agi::Context *c)
1071
956
        {
1072
957
                if (!has_config)
1073
958
                        return nullptr;
1077
962
                GetFeatureFunction("config");
1078
963
 
1079
964
                // prepare function call
1080
 
                auto subsobj = new LuaAssFile(L, c->ass);
 
965
                auto subsobj = new LuaAssFile(L, c->ass.get());
1081
966
                // stored options
1082
967
                lua_newtable(L); // TODO, nothing for now
1083
968
 
1093
978
                        config_dialog = new LuaDialog(L, false);
1094
979
                }
1095
980
 
1096
 
                return config_dialog;
 
981
                return std::unique_ptr<ScriptDialog>{config_dialog};
1097
982
        }
 
983
}
1098
984
 
 
985
namespace Automation4 {
1099
986
        LuaScriptFactory::LuaScriptFactory()
1100
987
        : ScriptFactory("Lua", "*.lua,*.moon")
1101
988
        {
1104
991
        std::unique_ptr<Script> LuaScriptFactory::Produce(agi::fs::path const& filename) const
1105
992
        {
1106
993
                if (agi::fs::HasExtension(filename, "lua") || agi::fs::HasExtension(filename, "moon"))
1107
 
                        return agi::util::make_unique<LuaScript>(filename);
 
994
                        return agi::make_unique<LuaScript>(filename);
1108
995
                return nullptr;
1109
996
        }
1110
997
}