633 lines
17 KiB
Lua
633 lines
17 KiB
Lua
--[[
|
|
==README==
|
|
|
|
Lua Interpreter
|
|
|
|
This allows you to run Lua code on-the-fly on an .ass file. The code will
|
|
be applied to all the selected lines. A simple API is provided to make
|
|
modifying line properties more efficient.
|
|
|
|
Calling it a "Lua interpreter" may be a misnomer, but I can't think of
|
|
anything better at the moment.
|
|
|
|
The code the user inputs is run for each "section" of text, as marked by
|
|
the override blocks. A "section" of text is defined as the part of the
|
|
line that has all the same properties. For example, this line:
|
|
|
|
Never gonna {\fs200}give {\alpha&H55&}you up
|
|
|
|
has three sections. The first section is "Never gonna " and contains all
|
|
default properties. The second section is "{\fs200}give ". All text in
|
|
this section has font size 200, and default properties otherwise. The
|
|
third and last section is "{\alpha&H55&}you up", which has font size 200,
|
|
an alpha of 55 hex, and default properties otherwise.
|
|
|
|
Any code you input into the interpreter will thus run once for each of
|
|
these three sections, changing the properties as appropriate.
|
|
|
|
Functions are as follows:
|
|
|
|
modify(tag, method)
|
|
mod(tag,method)
|
|
Modify tag using method. tag is a string that indicates the override
|
|
tag (property) that you want to modify. method is a function that
|
|
dictates how the modification is done. For example, to double the
|
|
font size, do:
|
|
|
|
modify("fs",multiply(2))
|
|
|
|
mod is an alias for modify, which you can use to save typing.
|
|
|
|
modify_line(property, method)
|
|
modln(propery,method)
|
|
Works like modify(), but acts on line properties, not override tags.
|
|
For example, to modify the layer of a line:
|
|
|
|
modify_line("layer",add(1))
|
|
|
|
For a list of line properties that can be modified, see:
|
|
http://docs.aegisub.org/3.0/Automation/Lua/Modules/karaskel.lua/#index12h3
|
|
|
|
add(...)
|
|
Returns a function that will add the given values. Can have multiple
|
|
parameters. For example, to expand a rectangular clip by 10 pixels on
|
|
all sides, assuming the first two coordinates represent top left and
|
|
the last two coordinates represent bottom right, do:
|
|
|
|
modify("clip",add(-10,-10,10,10))
|
|
|
|
This will add -10, -10, 10, and 10, in that order, to the four
|
|
parameters of \clip. There is no subtract() function; simply add a
|
|
negative number to subtract.
|
|
|
|
multiply(...)
|
|
mul(...)
|
|
Works like add(). There is no divide() function. Simply multiply by
|
|
a decimal or a fraction. Example:
|
|
|
|
modify("fscx",multiply(0.5))
|
|
|
|
replace(x)
|
|
rep(x)
|
|
Returns a function that returns x. When used inside modify(), this
|
|
will effectively replace the original parameter of the tag with x.
|
|
|
|
modify("fn",replace("Comic Sans MS"))
|
|
|
|
append(x)
|
|
app(x)
|
|
Returns a function that appends x to the parameter. For example:
|
|
|
|
modify_line("actor",append(" the great"))
|
|
|
|
I'm not sure why I wrote this function either. Completeness' sake,
|
|
perhaps.
|
|
|
|
get(tag)
|
|
Returns the parameter of the tag. If the tag has multiple parameters,
|
|
they are returned as a table. Example:
|
|
|
|
main_color=get("c")
|
|
|
|
remove(...)
|
|
rem(...)
|
|
Removes all the tags listed. Example:
|
|
|
|
remove("bord","shad")
|
|
|
|
select()
|
|
Adds the current line to the final selection. If this function is
|
|
never used, the original selection will be returned.
|
|
|
|
duplicate()
|
|
DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING. This will insert a
|
|
copy of the current line after the current line. Beware of recursion!
|
|
If you do not put some sort of if statement around duplicate(), then
|
|
your first line will be duplicated, then the duplicate will be
|
|
duplicated, then the duplicate of the duplicate will be duplicated,
|
|
and you end up in an infinite loop. I suggest you use the function
|
|
like this:
|
|
|
|
if i%2==1 then
|
|
duplicate()
|
|
|
|
--Code to run on the original line
|
|
|
|
else
|
|
|
|
--Code to run on the duplicate line
|
|
|
|
end
|
|
|
|
Note that "once per line" functions such as duplicate() are run at
|
|
the end of the rest of the execution, but before changes are saved.
|
|
In other words, duplicate() will always create a line that looks like
|
|
your current line did originally, before you modified it at all.
|
|
|
|
You also have access to all functions in utils.lua and karaskel.lua, such
|
|
as functions for doing math on alpha and color values. I may eventually
|
|
write alpha and color handling into the already complex modify function,
|
|
but for now, code such as modify("alpha",add(50)) will not work.
|
|
|
|
|
|
|
|
Global variables are as follows:
|
|
|
|
i
|
|
This is the index within your selection. In other words, when the
|
|
code is being run on the first line, i will have the value 1. When
|
|
the code is being run on the third line, i will have the value 3.
|
|
In the code example under duplicate() above, i will be odd for all
|
|
of the original lines and even for all of the duplicates, thus
|
|
the check "i%2==1" is made.
|
|
|
|
li
|
|
This is the line number of the current line.
|
|
|
|
j
|
|
This is the number (counting from 1) of the section that the code is
|
|
currently looking at.
|
|
|
|
state
|
|
This is a table containing the current state of the line, indexed by
|
|
tag name. For example, to find out what the current x scaling is, use:
|
|
|
|
state["fscx"]
|
|
|
|
This table automatically updates when your code modifies properties
|
|
of the line. To see the state of the untouched line, use the variable
|
|
dstate (for default state).
|
|
|
|
pos
|
|
This is a table (or object) with two fields: x and y. Use pos.x to
|
|
access the x coordinate and pos.y to access the y coordinate. The
|
|
coordinates are guaranteed to match the line's position on screen,
|
|
even if no position is defined in-line. You can perform arithmetic
|
|
on this object, but it may not behave the way you want it to. You
|
|
are advised to use modify("pos",...) instead.
|
|
|
|
org
|
|
Like pos, but for the origin.
|
|
|
|
flags
|
|
A global table for values you want to store outside of the loop. Most
|
|
other variables will change or be reset once the the script starts to
|
|
run on the next line. It's empty by default.
|
|
|
|
]]
|
|
|
|
script_name = "Lua Interpreter"
|
|
script_description = "Run Lua code on the fly."
|
|
script_version = "1.3.0"
|
|
script_author = "lyger"
|
|
script_namespace = "lyger.LuaInterpret"
|
|
|
|
local DependencyControl = require("l0.DependencyControl")
|
|
local rec = DependencyControl{
|
|
feed = "https://raw.githubusercontent.com/TypesettingTools/lyger-Aegisub-Scripts/master/DependencyControl.json",
|
|
{
|
|
{"lyger.LibLyger", version = "2.0.0", url = "http://github.com/TypesettingTools/lyger-Aegisub-Scripts"},
|
|
"aegisub.util"
|
|
}
|
|
}
|
|
|
|
local LibLyger, util = rec:requireModules()
|
|
local libLyger = LibLyger()
|
|
local f2s, esc = LibLyger.float2str, LibLyger.esc
|
|
|
|
--Set the location of the config file
|
|
local config_pre=aegisub.decode_path("?user")
|
|
local config_name="luaint-presets.config"
|
|
local psep=config_pre:match("\\") and "\\" or "/"
|
|
|
|
--Old config path, to allow old data to be copied over to the proper location
|
|
local old_config_path=config_pre..config_name
|
|
--Proper config path
|
|
local config_path=config_pre..psep..config_name
|
|
|
|
|
|
local textbox, dialog_conf = {}, {}
|
|
|
|
--Lookup table for once-per-line tags
|
|
local opl= {
|
|
["pos"]=true,
|
|
["org"]=true,
|
|
["move"]=true,
|
|
["a"]=true,
|
|
["an"]=true,
|
|
["fad"]=true,
|
|
["clip"]=true
|
|
}
|
|
|
|
--Remake the configuration defaults
|
|
local function make_conf()
|
|
textbox={class="textbox",name="code",x=0,y=1,width=40,height=6}
|
|
dialog_conf=
|
|
{
|
|
{class="label",label="Enter code below:",x=0,y=0,width=40,height=1},
|
|
textbox
|
|
}
|
|
end
|
|
|
|
|
|
--Returns a function that adds by each number
|
|
function add(...) -- exposed to script
|
|
local x = table.pack(...)
|
|
return function(...)
|
|
local y, z = table.pack(...), {}
|
|
for i=1, #y do
|
|
y[i]=tonumber(y[i]) or 0
|
|
x[i]=tonumber(x[i]) or 0
|
|
z[i]=y[i]+x[i]
|
|
end
|
|
return unpack(z)
|
|
end
|
|
end
|
|
|
|
--Returns a function that multiplies by each number
|
|
function multiply(...) -- exposed to script
|
|
local x = table.pack(...)
|
|
return function(...)
|
|
local y, z = table.pack(...), {}
|
|
for i=1, #y do
|
|
y[i]=tonumber(y[i]) or 0
|
|
x[i]=tonumber(x[i]) or 0
|
|
z[i]=y[i]*x[i]
|
|
end
|
|
return unpack(z)
|
|
end
|
|
end
|
|
|
|
--Returns a function that replaces with x
|
|
function replace(...) -- exposed to script
|
|
local args = table.pack(...)
|
|
return function() return unpack(args) end
|
|
end
|
|
|
|
--Returns a function that appends x
|
|
function append(x) -- exposed to script
|
|
return function(y) return y..x end
|
|
end
|
|
|
|
--Write presets table to file
|
|
local function table_to_file(path,wtable)
|
|
local wfile=io.open(path,"wb")
|
|
wfile:write("return\n")
|
|
LibLyger.write_table(wtable,wfile," ")
|
|
wfile:close()
|
|
end
|
|
|
|
--Read presets table from file
|
|
local function table_from_file(path)
|
|
local lfile=io.open(path,"r")
|
|
if not lfile then return end
|
|
local return_presets,err = loadstring(lfile:read("*all"))
|
|
if err then
|
|
aegisub.log(err)
|
|
return
|
|
end
|
|
lfile:close()
|
|
return return_presets()
|
|
end
|
|
|
|
|
|
function lua_interpret(sub,sel)
|
|
make_conf()
|
|
libLyger:set_sub(sub, sel)
|
|
|
|
--Copies old data over in the case of first run after upgrade
|
|
local oldpresets=table_from_file(old_config_path)
|
|
if oldpresets then
|
|
table_to_file(config_path,oldpresets)
|
|
end
|
|
|
|
--Load presets or create if none
|
|
local presets = table_from_file(config_path)
|
|
if not presets then
|
|
presets={["Example - Duplicate and Blur"] = [[
|
|
if i%2 == 1 then
|
|
duplicate()
|
|
modify("bord", replace(0))
|
|
if state.blur == 0 then modify("blur", replace(0.6)) end
|
|
modify_line("layer",add(1))
|
|
remove("3c", "3a", "shad")
|
|
else
|
|
modify("c", replace(get("3c")))
|
|
modify("1a", replace(get("3a")))
|
|
if state.blur == 0 then modify("blur", replace(0.6)) end
|
|
end]]}
|
|
|
|
table_to_file(config_path,presets)
|
|
end
|
|
|
|
--Components of the dialog
|
|
local preselector = {
|
|
class="dropdown",items={},
|
|
name="pre_sel",
|
|
x=0,y=7,width=20,height=1
|
|
}
|
|
local prenamer = {
|
|
class="edit",
|
|
name="new_prename",
|
|
x=20,y=7,width=20,height=1
|
|
}
|
|
|
|
dialog_conf[3], dialog_conf[4] = preselector, prenamer
|
|
|
|
local function make_name_list()
|
|
preselector.items = {}
|
|
local maxnew, p = 0, 1
|
|
for k,_ in pairs(presets) do
|
|
preselector.items[p], p = k, p+1
|
|
num=k:match("New preset (%d+)")
|
|
num=tonumber(num) or 0
|
|
maxnew = math.max(num, maxnew)
|
|
end
|
|
table.sort(preselector.items)
|
|
prenamer.value=string.format("New preset %d",maxnew+1)
|
|
end
|
|
|
|
make_name_list()
|
|
|
|
--Show GUI
|
|
local pressed, results
|
|
repeat
|
|
pressed,results=aegisub.dialog.display(dialog_conf,
|
|
{"Run","Load","Save","Delete","Cancel"})
|
|
|
|
if pressed=="Cancel" then aegisub.cancel() end
|
|
if pressed=="Load" then
|
|
textbox.value=presets[results["pre_sel"]]
|
|
preselector.value=results["pre_sel"]
|
|
end
|
|
if pressed=="Save" then
|
|
textbox.value=results["code"]
|
|
if presets[results["new_prename"]]~=nil then
|
|
aegisub.dialog.display({{class="label",label="Name already in use!",x=0,y=0,width=1,height=1}})
|
|
else
|
|
presets[results["new_prename"]]=results["code"]
|
|
table_to_file(config_path,presets)
|
|
make_name_list()
|
|
end
|
|
end
|
|
if pressed=="Delete" then
|
|
presets[results["pre_sel"]]=nil
|
|
make_name_list()
|
|
table_to_file(config_path,presets)
|
|
preselector.value=nil
|
|
end
|
|
|
|
until pressed=="Run"
|
|
|
|
local command, new_sel = results["code"], {}
|
|
|
|
--Run for all lines in selection. Hard limit of 9001 just in case
|
|
i, flags = 1, {} -- exposed to script
|
|
while i<=#sel and #sel<=9001 do
|
|
local li=sel[i]
|
|
local line, line_table = libLyger.lines[li], {}
|
|
|
|
aegisub.progress.set(100*i/#sel)
|
|
|
|
--Alias maxi to the size of the selection
|
|
maxi = #sel -- exposed to script
|
|
--Break the line into a table
|
|
|
|
if not line.text:match("^{") then
|
|
line.text="{}"..line.text
|
|
end
|
|
line.text=line.text:gsub("}","}\t")
|
|
local j = 1
|
|
for thistag,thistext in line.text:gmatch("({[^{}]*})([^{}]*)") do
|
|
line_table[j]={tag=thistag:gsub("\\1c","\\c"),text=thistext:gsub("^\t","")}
|
|
j=j+1
|
|
end
|
|
line.text=line.text:gsub("}\t","}")
|
|
|
|
--These functions are run at the end, at most once per line
|
|
local tasklist = {}
|
|
|
|
--Function to select line
|
|
local function _select()
|
|
tasklist[#tasklist+1] = function()
|
|
new_sel[#new_sel+1] = li
|
|
selected = true -- exposed to script
|
|
end
|
|
end
|
|
|
|
--Function to duplicate lines
|
|
local function _duplicate()
|
|
table.insert(tasklist,1,function()
|
|
table.insert(sel,i+1,li+1)
|
|
libLyger:insert_line(util.copy(line), li+1)
|
|
for x = i+2, #sel do
|
|
sel[x] = sel[x]+1
|
|
end
|
|
|
|
for x = 1, #new_sel do
|
|
if new_sel[x] > li+1 then
|
|
new_sel[x] = new_sel[x] + 1
|
|
end
|
|
end
|
|
|
|
duplicated = true -- exposed to script
|
|
flags["duplicate"]=true
|
|
end)
|
|
end
|
|
|
|
--Function to modify line properties
|
|
local function _modify_line(prop,func)
|
|
table.insert(tasklist, function()
|
|
line[prop]=func(line[prop])
|
|
end)
|
|
end
|
|
|
|
--Create state table
|
|
local state_table = {}
|
|
for j,a in ipairs(line_table) do
|
|
state_table[j]={}
|
|
for b in a.tag:gmatch("(\\[^\\}]*)") do
|
|
if b:match("\\fs%d") then
|
|
state_table[j]["fs"]=b:match("\\fs([%d%.]+)")
|
|
state_table[j]["fs"]=tonumber(state_table[j]["fs"])
|
|
elseif b:match("\\fn") then
|
|
state_table[j]["fn"]=b:match("\\fn([^\\}]*)")
|
|
elseif b:match("\\r") then
|
|
state_table[j]["r"]=b:match("\\r([^\\}]*)")
|
|
else
|
|
local tag, param = b:match("\\([1-4]?%a+)(%A[^\\}]*)")
|
|
state_table[j][tag] = tonumber(param) or param
|
|
end
|
|
end
|
|
end
|
|
|
|
--Create default state and current state
|
|
state = libLyger:style_lookup(line) -- exposed to script
|
|
dstate = util.copy(state) -- exposed to script
|
|
|
|
--Define position and origin objects
|
|
pos, org = {}, {} -- exposed to script
|
|
pos.x,pos.y=libLyger:get_pos(line)
|
|
org.x,org.y=libLyger:get_org(line)
|
|
|
|
--Now cycle through all tag-text pairs
|
|
for j,a in ipairs(line_table) do
|
|
local fenv = getfenv(1)
|
|
fenv.j=j
|
|
fenv.line=line
|
|
fenv.flags=flags
|
|
fenv.maxj=#line_table
|
|
|
|
--Wrappers for the once-per-line functions
|
|
fenv.duplicate = function()
|
|
if j==1 then _duplicate() end
|
|
end
|
|
fenv.select = function()
|
|
if j==1 then _select() end
|
|
end
|
|
fenv.modify_line = function(prop,func)
|
|
if j==1 then _modify_line(prop,func) end
|
|
end
|
|
|
|
local first = j==1
|
|
|
|
--Define variables
|
|
text, tag = a.text, a.tag -- exposed to script
|
|
|
|
--Update state
|
|
for tag, param in pairs(state_table[j]) do
|
|
state[tag]= param
|
|
dstate[tag]= param
|
|
end
|
|
|
|
--Get the parameter of the given tag
|
|
fenv.get = function(b)
|
|
local param = tostring(dstate[b])
|
|
if param:match("%b()") then
|
|
local c = {}
|
|
for d in param:gmatch("[^%(%),]+") do
|
|
c[#c+1] = d
|
|
end
|
|
return unpack(c)
|
|
end
|
|
return param
|
|
end
|
|
|
|
--Modify the given tag
|
|
fenv.modify = function(b,func)
|
|
--Make sure once-per-lines are only modified once
|
|
if opl[b] and j~=1 then return end
|
|
|
|
local c, d = {get(b)}
|
|
if #c==1 then c=c[1] end
|
|
if type(c)=="table" then
|
|
local e, h, f = {func(unpack(c))}, {"("}, ""
|
|
--If modifying pos or org, store values in relevant objects
|
|
if b=="pos" then pos.x,pos.y=unpack(e) end
|
|
if b=="org" then org.x,org.y=unpack(e) end
|
|
d = {"("}
|
|
for i, g in ipairs(e) do
|
|
d[2*i], d[2*i+1] = f, g
|
|
h[2*i], h[2*i+1] = f, c[i]
|
|
f = ","
|
|
end
|
|
d[#d+1], h[#h+1] = ")", ")"
|
|
c, d = table.concat(h), table.concat(d)
|
|
else
|
|
d=func(c)
|
|
if tonumber(d) then
|
|
d = f2s(tonumber(d))
|
|
end
|
|
end
|
|
--Prevent redundancy
|
|
if state[b]~=d then
|
|
local mod_tag, num = "\\"..b..esc(d)
|
|
tag, num = tag:gsub("\\"..b..esc(c), mod_tag)
|
|
if num<1 and not opl[b] then insert(mod_tag) end
|
|
state[b]=d
|
|
end
|
|
end
|
|
|
|
--Remove the given tags
|
|
fenv.remove = function(...)
|
|
tag = LibLyger.line_exclude(tag, table.pack(...))
|
|
end
|
|
|
|
--Insert the given tag at the end
|
|
fenv.insert = function(b)
|
|
tag=tag:gsub("}$",b.."}")
|
|
end
|
|
|
|
--Select every
|
|
fenv.isel = function(n)
|
|
if i%n==1 then select() end
|
|
end
|
|
|
|
--Aliases for common functions
|
|
fenv.mod=modify
|
|
fenv.mul=multiply
|
|
fenv.rep=replace
|
|
fenv.app=append
|
|
fenv.modln=modify_line
|
|
fenv.rem=fenv.remove
|
|
|
|
--Run the user's code
|
|
local com, err = loadstring(command)
|
|
|
|
if err then
|
|
aegisub.log(err)
|
|
aegisub.cancel()
|
|
end
|
|
|
|
setfenv(com, fenv)()
|
|
|
|
a.text=text
|
|
a.tag=tag
|
|
end
|
|
|
|
for _,task in ipairs(tasklist) do
|
|
task()
|
|
end
|
|
|
|
--Rebuild
|
|
local rebuilt_text = {}
|
|
for r, a in ipairs(line_table) do
|
|
rebuilt_text[r*2-1], rebuilt_text[r*2] = a.tag, a.text
|
|
end
|
|
line.text = table.concat(rebuilt_text):gsub("{}","")
|
|
|
|
--Update position and org
|
|
local px, py = libLyger:get_pos(line)
|
|
if px ~= pos.x or py ~= pos.y then
|
|
local ptag = string.format("\\pos(%s,%s)", f2s(pos.x), f2s(pos.y))
|
|
local num
|
|
line.text, num = line.text:gsub("\\pos%b()", esc(ptag))
|
|
if num < 1 then
|
|
line.text = line.text:gsub("{", "{"..esc(ptag), 1)
|
|
end
|
|
end
|
|
local ox, oy = libLyger:get_org(line)
|
|
if ox ~= org.x or oy ~= org.y then
|
|
local otag = string.format("\\org(%s,%s)", f2s(org.x), f2s(org.y))
|
|
local num
|
|
line.text, num = line.text:gsub("\\org%b()", esc(otag))
|
|
if num < 1 and (ox ~= pos.x or oy ~= pos.y) then
|
|
line.text = line.text:gsub("{", "{"..esc(otag), 1) end
|
|
end
|
|
|
|
--Reinsert
|
|
sub[li]=line
|
|
|
|
--Increment
|
|
i=i+1
|
|
end
|
|
|
|
aegisub.set_undo_point(script_name)
|
|
|
|
--Return new selection or old selection
|
|
if #new_sel>0 then return new_sel end
|
|
return sel
|
|
|
|
end
|
|
|
|
rec:registerMacro(lua_interpret) |