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
returns 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