x=5
y=12
z=x+y
..
operator.
first_name="John"
last_name="Smith"
full_name=first_name.." "..last_name
message="Hello world!"
\
) is an "escape character" (more info here). If you want to type a normal backslash, type "\\
".
true
or false
. You can use Booleans to evaluate if
conditions or loops.
is_happy=true
if is_happy then
print("Im happy too!")
end
true
or false
depending on the situation. The following are examples of boolean expressions.
x > y
number <= 0
count == 5
command ~= "quit"
x < 0 or x > 10
x > 0 and y > x
and
and or
.
count = 5
means "store the value 5 in count", which is not what we want here. To check for equality, use count == 5
, which means "count is equal to 5".
*
, division is /
, and exponentiation is ^
. The modulo operation is %
.
x=21
y=3*x+46
z=((y-x)/(y+x))^2
x=15+y
x
on line 4 does not affect y
or z
.
nil
that can be stored in any variable. It means "nothing". This is not the same as the number 0, nor is it the same as a string with no text in it. If a variable is nil
, it means "no valid data is stored in this variable". Uninitiated variables and failed function calls will generally result in nil
.
nil
values is an error that should be avoided, but nil
values can be very useful. For example, you can see whether tonumber(foo)
returns nil
(i.e. "failed to covert to number") to determine whether foo
is a valid number or not.
end
, if
, for
, or in
, because all of these serve other purposes in Lua.
if
statement. An if
statement checks whether a condition is true, and if it is, it performs all of the code until end
. Otherwise, it skips the code and continues after the end
.
x=5
y=10
z=0
if x<y then
z=x*y
print("Foobar")
end
print("The value of z is "..z)
x
is less than y
, the code inside the if
statement executes. z
is set to 50, and "Foobar" is printed out to the console. Afterwards, "The value of z is 50" is printed to the console as well.
if
statement using elseif
and else
.
if command=="quit" then
print("Goodbye!")
return
elseif command=="say hi" then
print("Hello, "..name.."!")
else
print("I didn't understand your command.")
end
while... do
and repeat... until
. These should be self-explanatory, so I'll just show some examples and move on.
x=100
y=0
while x>0 do
x=x-1
y=y+x
end
print("The value of y is "..y)
repeat
print("Say quit to exit this program")
user_input=io.read()
until user_input=="quit"
print("Goodbye!")
for
loop. The for
loop will cycle a variable or variables through a set of values, and execute the code in the loop once each time. The simplest use of a for
loop is as a counter, to cycle a variable from a starting value to an ending value. For example, to count from 1 to 10, we simply do:
for x=1,10,1 do
print(x)
end
for
statement says "define a variable named x
, which will start at 1, end at 10, and count by 1." The third number is optional; if you put for x=1,10 do
Lua will count by 1 by default. If you want to count by 2, then use for x=0,10,2 do
, and if you want to count down, use a negative number. The commands inside the for
statement (in this case, a print
) will be executed for each value of x.
for
to calculate the factorial of 10.
result=1
for num=1,10 do
result=result*num
end
print("10! = "..result)
num
stores values from 1 to 10, and the code inside the loop is executed once for each of those numbers, thus multiplying result
by all the numbers from 1 through 10.
my_array={8, 6, 7, 5, 3, 0, 9}
print(my_array[3])
my_array[3]
refers to the third value in the array. 7 is at the third position in the list, so the output of this code is 7.
for
loop to perform operations on every item in an array. By placing a #
in front of the name of the array, we can get the size of the array (the number of items in the list). We can use this as the upper bound for our for
loop.
my_array={8, 6, 7, 5, 3, 0, 9}
for i=1,#my_array do
print("The number at index "..i.." is "..my_array[i])
end
ipairs
function. The i presumably stands for indexed, meaning this function gives the elements of an array in order based on their index. The "pairs" is because it returns index-value pairs. You use ipairs
like this:
my_array={"Never", "gonna", "give", "you", "up"}
for index, value in ipairs(my_array) do
print("The string at index "..index.." is "..value)
end
array[2][3]
. Unlike many programming languages, Lua allows you to have different kinds of values stored in the same array. You can thus have a list of mixed numbers, strings, booleans, and tables.
person={
fname="John",
lname="Smith",
age=17,
favfood="pizza"
}
person["fname"]
. Lua also allows you to access the data in a way that looks more "object-oriented" (if you don't know what this is, don't worry about it). The code person.fname
means the exact same thing as person["fname"]
.
==
and other similar operators. Well, you can, but it probably won't do what you want it to.
function add_and_minus_four(num1, num2)
result=num1+num2-4
return result
end
return
s the result to us. You can use this function elsewhere in the program like this:
x=12
y=16
z=add_and_minus_four(x,y)
print(z)
z
. For reference, the above code can be written in one line as:
print(add_and_minus_four(12,15))
return
value, because unlike math functions, a function in a program can do tasks other than calculating a result. Functions also need not have any parameters, but when you use the function, you always need the parentheses, even if there is nothing in them.
return value1, value2, ...
at the end of your function. Then, when you use the function, store the multiple returns in variables separated by commas: x1, x2, ... = ...
.
return
statement. Any code after the return
statement will not execute. Thus, if you have a return
inside an if
statement, there is no need to put an else
.
function greater_of_two(num1, num2)
if num1 > num2 then
return num1
end
return num2
end
#
.
end
keyword. There is no begin
keyword; the function definition or the head of the control structure serves to begin the scope. An exception is repeat... until
, where the two keywords define the scope and no end
is needed.
local
keyword for variables and functions, if you need to control the scope. Generally you don't need to worry about this if there's no chance of confusion, but it's still good practice. Variables not declared local
are global.
nil
. This is more or less equivalent to the null
value in Java. Failed function calls often return nil
. This can be used, for example, to detect if a string does not contain a certain substring: string.match(str, substr) == nil
(incidentally, =
is assignment and ==
is equality).
nil
. A function can receive an arbitrary number of parameters using ...
(look it up).
\
). Remember to escape slashes when writing .ass override tags.
and
and or
. "Not equals to" is ~=
. String concatenation is ..
. You don't need semicolons at the ends of lines, and superfluous whitespace is ignored.
--This is a single line comment
--[[
This is a
multi-line
comment
]]
pairs
returns all the key-value pairs in a table, in both the array part and the hash table part. The pairs
function does not guarantee any order to the values it returns, not even for the array part of the table. You use the function like this:
for key, value in pairs(my_table) do
...
end
pairs
with ipairs
.
my_table["key1"]
as my_table.key1
.
pairs
and ipairs
functions, Lua also provides the useful tonumber
and tostring
functions.
table
library is worth looking into, but the only functions from it that I regularly use are table.insert
and table.remove
.
str
and a pattern named pat
that you want to match, you can use either string.match(str, pat)
or str:match(pat)
. This is a bit shorter and somewhat more intuitive, especially if you're used to object-oriented.
string.match
, string.format
, string.gmatch
, and string.gsub
. I used to use string.find
, but I found that in most cases, string.match
is a better option.
string.match
returns the section of the string that matches the pattern, or the captures if the pattern contained any (these are returned as multiple return values). If you've used format strings in C before, string.format
is basically the same thing. You can often get the same functionality just by concatenating strings (since numbers are automatically converted to string when concatenated), but it's often neater and more convenient to use string.format
.
string.gsub
. This is the bread and butter of most automation scripts that I've written, because most Aegisub automation involves modifying the text of your subtitle script. There's no better or more versatile way to modify text in Lua than string.gsub
. Its many capabilities can be overwhelming for some, so I've written an example script that should walk you through what you can do with it.
math.rad
and math.deg
to convert from degrees to radians and vice versa. Many a time I have been stymied by misbehaving code, only to realize I'd forgotten to convert. Also note that the math library includes the constant math.pi
.
math.random
will produce the exact same sequence of pseudorandom numbers. If you want to get a different pseudorandom number sequence, use math.randomseed
to seed the random number generator with a different number. A good solution is to use your constantly-changing system time as a seed: math.randomseed(os.time())
. This will produce a new sequence of numbers each time you run the automation... so long as you wait a second. Sadly, Lua doesn't do milliseconds.
--[[
README:
Put some explanation about your macro at the top! People should know what it does and how to use it.
]]
--Define some properties about your script
script_name="Name of script"
script_description="What it does"
script_author="You"
script_version="1.0" --To make sure you and your users know which version is newest
--This is the main processing function that modifies the subtitles
function macro_function(subtitle, selected, active)
--Code your function here
aegisub.set_undo_point(script_name) --Automatic in 3.0 and above, but do it anyway
return selected --This will preserve your selection (explanation below)
end
--This optional function lets you prevent the user from running the macro on bad input
function macro_validation(subtitle, selected, active)
--Check if the user has selected valid lines
--If so, return true. Otherwise, return false
return true
end
--This is what puts your automation in Aegisub's automation list
aegisub.register_macro(script_name,script_description,macro_function,macro_validation)
macro_function
(or whatever you choose to name your function) and execute the contents of the function. The function is given three parameters: the subtitle object, a list of selected lines, and the line number of the currently active line. Most scripts only use the first two. If that's the case, feel free to not include the active
parameter. Also, to save typing, I usually abbreviate the parameter names to sub, sel, act
. You can name the parameters whatever you want. Much like in math, f(x)=2x+3 is exactly the same as f(y)=2y+3.
sub, sel, act
to stand in for the three parameters in code examples.
sub[5]
.
line=sub[line_number]
. Then you modify the line, and put it back in the subtitles object with sub[line_number]=line
. Incidentally, these lines that you read out are line data tables, which I'll cover later.
sel
. To reiterate: only the subtitles object contains data about the script. You won't find the selected lines in sel
. Instead, you'll find a list of the line numbers of the selected lines. In other words, sel
is a list of indices for the subtitles object. To get the first selected line, you have to write sub[sel[1]]
. This can be a bit confusing, especially if you use ipairs
on sel
, as I will show you how to do later. You'll end up with two indices: one that refers to a position in sel
and one that refers to a position in sub
. Don't get confused.
sub[act]
.
function do_for_selected(sub, sel, act)
--Keep in mind that si is the index in sel, while li is the line number in sub
for si,li in ipairs(sel) do
--Read in the line
line = sub[li]
--Do stuff to line here
--Put the line back in the subtitles
sub[li] = line
end
aegisub.set_undo_point(script_name)
return sel
end
line.text
. If you just want to take care of some simple text modifications such as adding italics, or even some basic math like shifting position, then you have all you need to know. You can stop reading after the following two example scripts.
math.floor
is the function that rounds down to the nearest whole number (the opposite is math.ceil
).
string.gsub
with an anonymous function. If you're having trouble with understanding this use of string.gsub
, you can check out the example script I wrote, or the string library tutorial on the Lua users wiki. The pattern "\\fs(%d+)"
looks for the string "\fs" followed by a number, and the parentheses around %d+
will "capture" the number and send it to the anonymous function (another reminder that you have to escape backslashes).
string.format
to insert values into a format string. The formatted string is then returned by the function, and substituted into the original string.
include("karaskel.lua")
karaskel.collect_head
, which collects the meta and style data that other karaskel functions use. You'll need a line at the top of your processing function that looks something like this:
local meta, styles = karaskel.collect_head(sub,false)
false
, because true
will generate a lot of mysterious extra styles in your script. That's not where the magic happens, though. After you've read in your line to process it, you can do this:
karaskel.preproc_line(sub,meta,styles,line)
line.styleref
, which is exciting. Seriously. Be excited.
line.styleref.color1
to figure out what the default main color of your line is. You can check the default font size using line.styleref.fontsize
. Need to know the border weight? line.styleref.outline
is your friend.
\c
tags just yet. Color codes in style definitions contain both color and alpha data, and look a bit different from in-line override codes. You'll need a function from utils.lua to extract properly formatted color and alpha codes for use in override tags.
aegisub.text_extents
function, found in the miscellaneous APIs. You'll need to use karaskel.collect_head
before you can use this function, which is why I mention it here. This function takes a style table and a bit of text and calculates the pixel height and width of the text. If you pass it line.styleref
, then it will give you the size of the text in the line's default style. But you can do more.
aegisub.text_extents
. You can determine the size of any typeset that the user makes, even if he changes the font or font size in the line. That opens up a lot of possibilities.
include("utils.lua")
table.copy
function. If you want to create new lines in your subtitle script, you're going to need this function. As mentioned in an earlier section, copying a table is more involved than copying a number or a string. To create a new line, you're going to have to make a proper copy of your original line data table, and you'll need this function to do that.
aegisub.dialog.display
. This function takes two parameters, both of which are tables. The second table is easy enough. It's just a list of the buttons you want at the bottom of the table. Something like {"Run", "Options", "Cancel"}
would work. The function's first return value is the button that was pressed.
dialog_config=
{
{
class="dropdown",name="lineselect",
x=0,y=0,width=1,height=1,
items={"Apply to selected lines","Apply to all lines"},
value="Apply to selected lines"
},
{
class="checkbox",name="unitalic",
x=1,y=0,width=1,height=1,
label="Unitalicize already italic lines",
value=false
},
{
class="label",
x=0,y=1,width=1,height=1,
label="\\fax value:"
},
{
class="floatedit",name="faxvalue",
x=1,y=1,width=1,height=1,
value=0
}
}
aegisub.dialog.display(dialog_config)
, we see this:
aegisub.dialog.display
. The keys in this hash table are the names of the components that we defined in the dialog configuration table. If, in our example, we store our results in a table named results
, then to access the selected option in the dropdown box we use results["lineselect"]
. To see whether the checkbox was checked, we'll see if results["unitalic"]
is true
or false
. To get the value we should use in the "\fax" tag, simply take a look at the number in results["faxvalue"]
.
buttons={"Italicize","Cancel"}
dialog_config= --See above
pressed, results = aegisub.dialog.display(dialog_config,buttons)
if pressed=="Cancel" then
aegisub.cancel()
end
--Handle the results table...
aegisub.decode_path
function that's very useful if you want to save and load files. Aegisub defines several helpful path specifiers that let you access directories such as the application data folder or the location of the video file.
{\fscx120\fs40}Never {\c&H0000FF&}gonna {\fs80}give {\fscy50}you {\fs69\fscx40\fscy115}up
tt_table | |||||||||||||||||||||||||||||||||||||||||||||
|
tt_table[3].text
is "give", while tt_table[2].tag
is "{\c&H0000FF&}". Plus, since an override tag affects everything to the right of it until it gets overridden again, we know that the contents of tt_table[2].tag
are going to affect all the text stored in tt_table[3]
through tt_table[5]
. In other words, we can start at the left side of the table and move to the right, and at any point in the table we'll know exactly how the text will be rendered, based on all the override tags we've seen so far.
tt_table | ||||||||||||||||||||||||||||||||||||||||||||||||
|
tt_table[1].tag
, and use them to update our state variables. Since there is no \fscy tag, cur_fscy
remains unchanged.
tt_table | ||||||||||||||||||||||||||||||||||||||||||||||
|
tt_table | |||||||||||||||||||||||||||||||||||||||||||||||
|
tt_table[3]
.
tt_table | ||||||||||||||||||||||||||||||||||||||||||||||||
|
tt_table | |||||||||||||||||||||||||||||||||||||||||||||||||
|
tt_table | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
tt_table | |||||||||||||||||||||||||||||||||||||||||||||
|
{\fs40\fscx1200\fscy1000}Never {\c&H0000FF&}gonna {\fs8\fscx1200\fscy1000}give {\fscy500}you {\fs6\fscx460\fscy1322}up