Originally committed to SVN as r2.

This commit is contained in:
Rodrigo Braz Monteiro 2006-01-16 21:02:54 +00:00
parent 4e9ac0ded4
commit a8f7fb667b
356 changed files with 50526 additions and 0 deletions

View file

@ -0,0 +1,548 @@
Aegisub Automation documentation
Version 3
Copyright 2005 Niels Martin Hansen.
---
This document describes version 3 of the automation system used in Aegisub.
The automation system uses the Lua language for scripting engine.
See <http://www.lua.org/> for more information.
---
What is Automation?
Aegisub Automation is a scripting system designed to automate many processing
tasks of ASS subtitles, instead of using tedious, error-prone manual
processing. The primary purpose is creating karaoke effects for anime fansubs.
The Automation script is given the complete subtitle data from a subtitle
file, in a format suited for creating special effects.
The script will return a complete substiture for the original subtitle
data, allowing full freedom of processing.
A number of helper functions are provided, to aid in finding errors in
scripts, as well as retrieve further data about the subtitles, needed to
created advanced effects.
---
Scripts, files, functions:
A script is a file containing Lua code. One file can define just one script,
but several scripts can share code by the help of including other files.
All scripts are run in a separate interpreter, and as such don't have any way
of interacting with other loaded scripts.
All strings in a script should be in UTF-8 encoding, without byte-order mark.
All strings input to a script are encoded as UTF-8.
Script files may start with an UTF-8 BOM (byte-order mark) or not, but this
is currently not well tested.
A script must define certain global variables:
version
Number. Version of the scripting interface used.
The version described in this file is 3.
To comply with version 3, version must be: 3 <= version < 4
kind
String. Not used, but mandatory. Set it to "basic_ass" for now.
name
String. Displayed name of the script.
description
String. Optional. Long description of the script.
process_lines
Function. The main script function.
configuration
Table. Optional. Configuration options for the script.
The functions are described in detail in the following.
The script may define further global variables, but they do not have any
special meaning. Be aware, however, that later versions of the scripting
system might define further global variables with special meanings, so be
careful choosing names for private use globals.
It's recommended to prefix private global variables with "p_"; the scripting
system will never assign special meanings to global variables with that
prefix.
The scripting system defines a global variable with name "aegisub", which
contains important values. You should not hide the "aegisub" variable.
---
The processing function:
The processing function is the heart of the script.
It takes as input some meta-information about the subtitles, the styles
used in the subtitles, as well as the actual subtitle data to process.
The output is a set of subtitle data in the same format as the input.
The output subtitle data will be used as a complete replacement of the
input data.
Future versions might allow modifying style data and meta data as well.
The processing function is defined as follows:
function process_lines(meta, styles, lines, config)
The arguments are:
@meta
Table. Meta information about the script. (Script Info section.)
@styles
Table. Style definitions. (V4+ Styles section.)
@lines
Table. Subtitle events. (Events section.)
@config
Table. Values set for the configuration options provided. If no
configuration options were provided, this will be an empty table.
Returns: One value.
This value must be a table, using the same format as @lines.
Note that the indexes in the return value may be either zero-based or
one-based, to allow for greater compatibility. You are encouraged to
use one-based indexes.
Description of @meta:
This is a table with the following keys:
res_x
Horizontal resolution of the script.
res_y
Vertical resolution of the script.
Description of @styles:
This is a table with the following keys:
-1
Number. The amount of styles defined, called "n".
0 -> n-1
Table. The actual style definitions.
<string "name">
Table. The style definition with the specified name.
The key -1 is used for count rather than "n", since one might have a style
definition with the name "n".
A style definition is a table with the following keys:
name
String. Name of the style.
fontname
String. Name of the font used.
fontsize
Number. Size of the font used.
color1
String. Primary color.
All color fields use raw hexadecimal format, that is, no special characters
before or after the hex string.
color2
String. Secondary color.
color3
String. Outline color.
color4
String. Shadow color.
bold
Boolean. Bold text or not.
italic
Boolean. Italic text or not.
underline
Boolean. Underlined text or not.
strikeout
Boolean. Striked-out text or not.
scale_x
Number. Horizontal scale.
scale_y
Number. Vertical scale.
spacing
Number. Spacing between characters.
angle
Number. Rotation angle in degrees.
borderstyle
Number. 1=Outline + drop shadow, 3=Opaque box (not really used???)
outline
Number. Thickness of outline.
shadow
Number. Distance of shadow from text.
align
Number. Numpad style alignment.
margin_l
Number. Left margin in pixels.
margin_r
Number. Right margin in pixels.
margin_v
Number. Vertical margin in pixels.
encoding
Number. Font encoding used.
Description of @lines:
This is a table with the following keys:
n
Number. The amount of lines.
0 -> n-1
Table. The actual lines.
A line is a table with the following key:
kind
String. Can be "blank", "scomment", "comment" or "dialogue".
The keys otherwise defined depends on the kind of the line.
If the kind if "blank", no further fields are defined.
If the kind is "scomment", the line is a "semicolon comment", and the
following key is defined:
text
String. Text following the semicolon until end of line. EOL not included.
If the kind is "comment" or "dialogue", the line is either a Comment: or
a Dialogue: line. In both cases, the following keys are defined:
layer
Number.
start_time
Number. Start time of line in centiseconds.
(Might change to userdata later.)
end_time
Number. End time of line in centiseconds.
(Might change to userdata later.)
style
String. Style used for this line.
name
String. Character name speaking this line.
margin_l
Number. Left margin override, in pixels. (0=no override)
margin_r
Number. Right margin override, in pixels. (0=no override)
margin_v
Number. Right margin override, in pixels. (0=no override)
effect
String. Effect to apply to the line. (No error checking done.)
text
String. Text to display.
text_stripped
String. Same as text, but stripped for all tags, and newline/hardspace
tags are converted to real newlines/spaces. Non-hard spaces at the start/
end of lines are stripped.
karaoke
Table. Line split into karaoke syllables. See below for more information.
Note about output:
Neither text_stripped nor karaoke are used when the results are parsed, they
are only passed to simplify processing. You should set text to the final text
of the line, you want in the output.
It is encouraged to entirely leave text_stripped and karaoke out of the
tables in the result.
Karaoke tables:
A karaoke table has a number of values indexed by numbers. Each value
represents a karaoke syllable.
Key "n" holds the number of syllables. The syllables can be accessed from
index 0 and up. The syllables are indexed chronologically.
A karaoke table always has at least one syllable. The first syllable (index
0) contains all data before the first timed syllable.
Each syllable is a table containing the following keys:
duration
Number. Duration of the syllable in centiseconds. Always 0 for first
syllable.
kind
String. "Kind" of the karaoke, the name of the tag. For a \k type syllable,
kind is "k", for a \kf syllable kind is "kf". Freeform tags can be used, as
long as they start with the letter "k" or "K".
Always the empty string ("") for the first syllable.
text
String. Text of the syllable. This includes formatting tags.
For the first syllable, this contains everything before the first karaoke
timing tag.
text_stripped
String. Same as text, but with all formatting tags stripped.
Description of @config:
This is a table. The keys are the names for the options defined in the global
"configuration" table. The values are the values provided by the user.
---
Script configuration:
An automation script can provide a configuration set, allowing the user to
set certain options before the script is called.
This is performed through the "configuration" value.
Scripts can define configuration options of the following types:
label
A static, non-editable text displayed to the user. (Useful for adding
additional explanations for some options.)
text
Freeform text entry.
int
Integer numbers. A range of valid values can be specified.
float
Any kind of number. A range of valid values can be specified.
bool
A boolean on/off value.
colour
An RGB colour value.
style
The name of a style defined in the subtitles.
The "configuration" table:
The "configuration" table contains a number of values indexed by numbers.
Each value defines a configuration option.
The configuration options must be in keys numbered from 1 to n, where n
is the number of options. No "n" key is required.
The configuration options will be presented to the user in the order defined.
Each configuration option is a table containing the following keys:
name
String. The internal name used to refer to the configuration option.
Must not contain the colon or pipe characters. (ASCII 58 and 124.)
kind
String. One of "label", "text", "int", "float", "bool", "colour" and
"style". Defines what kind of option this is.
label
String. Name of the option, presented to the user. Should be very short.
hint
String. Longer description of the option, presented to the user as a
tooltip. Ignored for "label" kind options.
min
Number. Optional. Lowest value allowed. Only used for "int" and "float" kinds.
max
Number. Optional. Highest value allowed. Only used for "int" and "float" kinds.
default.
Type depends on "kind". The value given to this configuration option before
the user has entered another value. Ignored for "label" kind options.
Data types for the different kinds:
label
None. A label doesn't have a value, and won't be present in the @config
table in the process_lines function.
text
String. You might want to do some kind of extra validation on text input, as
it might be anything.
int
Number. Guaranteed to always be integer.
float
Number. Can be integer or not.
bool
Boolean.
colour
String. An ASS hex colourcode in "&HBBGGRR&" format.
style
String. The name of the style. The style can't be guaranteed to exist, as
another export filter in Aegisub might have removed it before your script
gets to run.
---
Script environment and registration:
A script is assigned to a subtitle file by adding it to the
"Automation Scripts" extra header in the [Script Info] section. This header
contains a list of script filenames, separated by pipe characters. Example:
Automation Scripts: test1.lua|test2.lua
All scripts run in their own separate interpreter. This means there is no
risk of name collisions, though also that scripts can't easily share code.
If you need to share code between several scripts, you should create a
subdirectory to the script directory, and place include files there.
The settings for the configuration options for a script are stored in the ASS
file in the following way:
Each script gets one line for configuration, named "Automation Settings" plus
a space plus the filename of the script. The filename used is stripped of all
path specifiers. (Use unique filenames for your scripts!)
The value of the line is a pipe-separated list of "name:value" pairs. The name
is the internal name given by the "name" key. It is not mangled in any way.
The way the value is stored depends on the kind of the option.
label
Not stored.
text
The string is stored in an URL-encoding like manner. Some unsafe characters
are replaced with escape-sequences of the form #xx, where xx is a two-digit
hexadecimal number for the ASCII code of the escaped character. Only ASCII-
characters can be escaped this way, Unicode characters aren't supported.
int
Stored in ASCII base 10 without any group separators.
float
Stored in exponential notation, using ASCII base 10. (As the %e sprintf()
argument.)
bool
True is stored as "1", false as "0".
colour
Stored as in ASS hex format without any mangling.
style
Stored in the same manner as "text" kind options.
---
Helper functions:
There is a gloabl variable names "aegisub". This is a table containing
various helper functions.
The following helper functions are defined:
function aegisub.set_status(text)
Sets the current status-message. (Used for progress-reporting.)
@text
String. The status message.
Returns: nothing.
function aegisub.output_debug(text)
Output text to a debug console.
@text
String. The text to output.
Returns: nothing.
function aegisub.colorstring_to_rgb(colorstring)
Convert an ASS color-string to a set of RGB values.
@colorstring
String. The color-string to convert.
Returns: Four values, all numbers, being the color components in this
order: Red, Green, Blue, Alpha-channel
function aegisub.report_progress(percent)
Report the progress of the processing.
@percent
Number. How much of the data have been processed so far. (Percent)
Returns: nothing.
function aegisub.text_extents(style, text)
Calculate the on-screen pixel size of the given text using the given style.
@style
Table. A single style definition like those passed to process_lines.
@text
String. The text to calculate the extents for. This should not contain
formatting codes, as they will be treated as part of the text.
Returns 4 values:
1: Number. Width of the text, in pixels.
2: Number. Height of the text, in pixels.
3: Number. Descent of the text, in pixels.
4: Number. External leading for the text, in pixels.
Short description of the values returned:
Width: The X advance of the text, how much the "cursor" moves forward when
this text is rendered.
Height: The total height of the text, including internal leading.
Descent: How far below the baseline a character can extend. The ascent of
the text can be calculated as (height - descent).
External leading: How much vertical spacing will be added between the lines
of text rendered with this font. The total height of a line is
(height + external_leading).
function aegisub.frame_from_ms(ms)
Return the video frame-number for the given time.
@ms
Number. Time in miliseconds to get the frame number for.
Returns: A number, the frame numer. If there is no framerate data, returns
nil.
function aegisub.ms_from_frame(frame)
Returns the start-time for the given video frame-number.
@frame
Number. Frame-number to get start-time from.
Returns: A number, the start-time of the frame. If there is no framerate
data, returns nil.
function include(filename)
Include the named script. The script search-path defined in Aegisub will be
used, searching for the script.
If the filename is relative, the regular search path will not be used, but
instead the filename will be taken as relative to the directory the current
script is located in.
Note that if you use include() inside an included script, relative paths
will still be taken relative to the original script, and not relative to the
current included script. This is a design limitation.
The included script is loaded as an anonymous function, which is executed in
the current environment. This has two implications: You can include files
based on conditional statements, and even in loops, and included files can
return values using the "return" statement.
@filename
String. Name of the file to include.
Returns: Depends on the script included.
Note that if the file couldn't be found, the script will be terminated
(or fail to load.)
---
Versions of the scripting interface:
Here's a quick history of the scripting interface:
Version 1
Using Lua as engine.
The scripts used in the Karaoke Effector application, avaible at:
<http://www.jiifurusu.dk/files/programming/effector/>
Version 2
Using Python as engine.
The first draft for an Aegisub automation engine.
Never implemented.
Version 3
Using Lua as engine.
The current version.

View file

@ -0,0 +1,16 @@
-- Aegisub Automation demonstration script
-- Original written by Niels Martin Hansen
-- Given into the public domain
version = 3
kind = "basic_ass"
name = "Minimal demonstration"
description = "A very minimal demonstration of the strucrure of an Automation script."
configuration = {}
function process_lines(meta, styles, lines, config)
aegisub.report_progress(50)
aegisub.output_debug("Test script 1 running")
aegisub.report_progress(100)
return lines
end

View file

@ -0,0 +1,43 @@
-- Aegisub Automation demonstration script
-- Original written by Niels Martin Hansen
-- Given into the public domain
version = 3
kind = "basic_ass"
name = "Reading data demonstration"
description = "This is a demonstration of how to access the various data passed to an Automation script. It loops over the data structures provided, and dumps them to the debug console."
configuration = {}
function process_lines(meta, styles, lines, config)
out = aegisub.output_debug
out(string.format("Metadata: res_x=%d res_s=%d", meta.res_x, meta.res_y))
numstyles = styles[-1]
out("Number of styles: " .. numstyles)
for i = 0, numstyles-1 do
out(string.format("Style %d: name='%s' fontname='%s'", i, styles[i].name, styles[i].fontname))
end
out("Number of subtitle lines: " .. lines.n)
for i = 0, lines.n-1 do
aegisub.report_progress(i/lines.n*100)
if lines[i].kind == "dialogue" then
out(string.format("Line %d: dialogue start=%d end=%d style=%s", i, lines[i].start_time, lines[i].end_time, lines[i].style))
out(" Text: " .. lines[i].text)
out(" Stripped text: " .. lines[i].text_stripped)
out(" Number of karaoke syllables: " .. lines[i].karaoke.n)
for j = 0, lines[i].karaoke.n-1 do
syl = lines[i].karaoke[j]
extx, exty, extd, extl = aegisub.text_extents(styles[lines[i].style], syl.text_stripped)
out(string.format(" Syllable %d: dur=%d kind=%s text='%s' text_stripped='%s' extx=%d exty=%d extd=%d extl=%d", j, syl.duration, syl.kind, syl.text, syl.text_stripped, extx, exty, extd, extl))
--out(string.format(" Syllable %d: kind=%s", j, syl.kind))
end
else
out(string.format("Line %d: %s", i, lines[i].kind))
end
end
-- but really just do nothing
return lines
end

View file

@ -0,0 +1,16 @@
-- Aegisub Automation demonstration script
-- Original written by Niels Martin Hansen
-- Given into the public domain
include("utils.lua")
name = "Include demo"
description = "Simple demonstration of the include function."
version, kind, configuration = 3, 'basic_ass', {}
function process_lines(meta, styles, lines, config)
lines[lines.n] = copy_line(lines[0])
lines.n = lines.n + 1
return lines
end

View file

@ -0,0 +1,55 @@
-- Aegisub Automation demonstration script
-- Original written by Niels Martin Hansen
-- Given into the public domain
include("utils.lua")
name = "Text placement demo"
description = "Demonstration of the text_extents function, to do per-syllable placement of text."
version, kind, configuration = 3, 'basic_ass', {}
function process_lines(meta, styles, lines, config)
-- Prepare local variables
local output = { n=0 }
-- Loop through every line
for i = 0, lines.n-1 do
aegisub.report_progress(i/lines.n*100)
-- Only process dialogue lines
if lines[i].kind ~= "dialogue" then
table.insert(output, lines[i])
else
-- This is just for making the code a bit easier to read
local line = lines[i]
-- Get the rendered size of the entire line. (Won't work if there's line breaks in it.)
local totalx, totaly = aegisub.text_extents(styles[line.style], line.text_stripped)
-- Calculate where the first syllable should be positioned, if the line is to appear centered on screen
local curx, cury = (meta.res_x - totalx) / 2, meta.res_y / 2
-- And more preparations for per-syllable placement
local startx = curx
local tstart, tend = 0, 0
-- Now process each stllable
for j = 1, line.karaoke.n-1 do
-- A shortcut variable, and, most important: a copy of the original line
local syl, syllin = line.karaoke[j], copy_line(line)
-- Calculate the ending time of this syllable
tend = tstart + syl.duration*10
-- Get the rendered size of this syllable
local extx, exty, extd, extl = aegisub.text_extents(styles[line.style], syl.text_stripped)
-- Some debug stuff...
aegisub.output_debug(string.format("text_extents returned: %d, %d, %d, %d", extx, exty, extd, extl));
-- Replace the text of the copy of the line with this syllable, moving around
syllin.text = string.format("{\\an4\\move(%d,%d,%d,%d,%d,%d)\\kf%d\\kf%d}%s", curx, cury, curx, cury-exty, tstart, tend, tstart/10, syl.duration, syl.text)
-- Add the line to the output
table.insert(output, syllin)
-- And prepare for next iteration
curx = curx + extx
tstart = tend
end
-- More debug stuff
aegisub.output_debug(string.format("after syllable loop: totalx=%d curx-startx=%d", totalx, curx-startx))
end
end
-- And remember to return something :)
return output
end

View file

@ -0,0 +1,72 @@
-- Aegisub Automation demonstration script
-- Original written by Niels Martin Hansen
-- Given into the public domain
name = "Configuration demo"
description = "This script allows the user to input some data in the Aegisub Export window. Some of these data are used during the processing, to change the subtitles. (The strings is prepended every dialogue line.)"
configuration = {
[1] = {
name = "thelabel";
kind = "label";
label = "This is a label control. Just for shows.";
hint = "Tooltip for label?!?";
};
[2] = {
name = "thestring";
kind = "text";
label = "String:";
hint = "The string to insert at the beginning of each line";
default = "foobar "
};
[3] = {
name = "theint";
kind = "int";
label = "Integer:";
hint = "An integer number to display in debug output";
default = 50;
min = 0;
max = 100;
};
[4] = {
name = "thefloat";
kind = "float";
label = "Float number:";
hint = "Just a random float number";
default = 3.1415927;
};
[5] = {
name = "thebool";
kind = "bool";
label = "I accept";
hint = "Check if you accept the terms of the license agreement";
default = false;
};
[6] = {
name = "thecolour";
kind = "colour";
label = "Favourite color:";
hint = "What color do you want your pantsu?";
default = "&H8080FF";
};
[7] = {
name = "thestyle";
kind = "style";
label = "Style:";
hint = "Pick a style the effects will apply to, or none to apply to everything";
default = "";
}
}
version, kind = 3, 'basic_ass'
function process_lines(meta, styles, lines, config)
aegisub.output_debug("The string entered is: " .. config.thestring)
for i = 0, lines.n-1 do
aegisub.report_progress(i/lines.n*100)
if lines[i].kind == "dialogue" then
lines[i].text = config.thestring .. lines[i].text
end
end
return lines
end

View file

@ -0,0 +1,87 @@
-- Aegisub Automation demonstration script
-- Original written by Niels Martin Hansen
-- Given into the public domain
-- Define some required constants
-- version and kind are required to have the given values for the script to work.
-- configuration is not needed in this script, so it's just left as an empty table
version, kind, configuration = 3, "basic_ass", {}
-- Define the displayed name of the script
name = "Simple karaoke effect"
-- A longer description of the script
description = "A simple karaoke effect, with the source code heavily commented. Provided as a starting point for a useful effect."
-- The actual script function
function process_lines(meta, styles, lines, config)
-- Create a local variable to store the output subtitles in
local output = { n=0 }
-- Start to loop over the lines, one by one
-- The lines are numbered 0..n-1
for i = 0, lines.n-1 do
-- Show the user how far the script has got
aegisub.report_progress(i/lines.n*100)
-- First check if the line is even a dialogue line. If it's not, no need to process it.
if lines[i].kind ~= "dialogue" then
table.insert(output, lines[i])
else
-- This is a dialogue line, so process is
-- Make a nicer name for the line we're processing
newline = lines[i]
-- Also show the line to the user
aegisub.set_status(newline.text_stripped)
-- The text of the new line will be build little by little
-- Each line has 700 ms fadein, 300 ms fadeout,
-- is positioned at the center of the screen (\an8)
-- and the highlighting should be delayed by 1000 ms (100 cs)
newline.text = string.format("{\\fad(700,300)\\pos(%d,30)\\k100}", meta.res_x/2)
-- Make the line start 1000 ms (100 cs) earlier than original
newline.start_time = newline.start_time - 100
-- Now it's time to loop through the syllables one by one, processing them
-- The first syllable is usually a "null" syllable, not containing real data, so that one should be skipped.
-- This variable is used to keep track of when the last syllable ended
-- It's initialised to 1000, since the start of the line was pushed 1000 ms back
local cursylpos = 1000
for j = 1, lines[i].karaoke.n-1 do
local syl = lines[i].karaoke[j]
-- Call another function to process the syllable
newline.text = newline.text .. doSyllable(syl.text, cursylpos, cursylpos+syl.duration*10, syl.duration, syl.kind)
-- Calculate the start time of the next syllable
cursylpos = cursylpos + syl.duration*10
end
-- The entire line has been calculated
-- Add it to the output
table.insert(output, newline)
end
end
-- All lines processed, and output filled
-- Just return it
-- (This is important! If you don't return anything, the output file will be empty!)
return output
end
-- This effect was originally written in the "Effector" program, which can be considered the first version of Automation.
-- This following function is almost verbatimly copied from that original script.
-- This is done in order to show how you can make sub-functions to make your script more readable.
-- The contents of this function could also just be pasted into the middle of the main loop in process_lines,
-- but that generally makes scripts harder to read.
function doSyllable(text, t_start, t_end, t_dur, ktype)
-- Declare two local variables needed here
-- (If they're not declared local, they will be global.)
local a, b
-- If it's a "long" syllable, let the effect be different
if t_dur > 75 then
a = t_start + 500
b = t_end
else
a = t_start + 100
b = t_start + 500
end
-- Return the replacement for the syllable, including some ASS tags for format it
return string.format("{\\r\\t(%d,%d,\\1c&H808080&\\2c&H808080&)\\kf%d}%s", a, b, t_dur, text)
end

View file

@ -0,0 +1,189 @@
-- Aegisub Automation demonstration script
-- Original written by Niels Martin Hansen
-- Given into the public domain
-- But still, please don't use the effect generated by this script
-- (unchanged) for your own works.
include("utils.lua")
name = "Advanced karaoke effect"
description = "An advanced karaoke effect, making heavy use of both line-copying and per-syllable text placement. Also demonstrates how to treat lines differently, based on their style, and syllables differently based on the timing tag used."
version, kind, configuration = 3, 'basic_ass', {}
function process_lines(meta, styles, lines, config)
local output = { n=0 }
math.randomseed(5922) -- just to make sure it's initialised the same every time
for curline = 0, lines.n-1 do
aegisub.report_progress(curline/lines.n*100)
local lin = lines[curline]
if lin.kind == "dialogue" and lin.style == "op romaji" then
doromaji(lin, output, styles["op romaji"], 30)
elseif lin.kind == "dialogue" and lin.style == "op kanji" then
-- Just one of these lines should be uncommented.
-- This script was written before configuration worked, otherwise it would use configuration
-- to select which of the two effects to use for "op kanji" style lines.
--doromaji(lin, output, styles["op kanji"], 20)
dokanji(lin, output, styles["op kanji"])
else
-- Unknown lines are copied verbatim
table.insert(output, lin)
end
end
return output
end
function doromaji(lin, output, sty, linetop)
aegisub.set_status(lin.text_stripped)
--local linetop = 50
-- prepare syllable data
local linewidth = 0
local syltime = 0
local syls = {n=0}
for i = 1, lin.karaoke.n-1 do
local syl = lin.karaoke[i]
syl.width, syl.height, syl.descent, syl.extlead = aegisub.text_extents(sty, syl.text_stripped)
syl.left = linewidth
syl.start_time = syltime
syl.end_time = syltime + syl.duration
syltime = syltime + syl.duration
if syl.kind == "kf" and (syl.text == " " or syl.text == " ") then
-- The font this effect was made to work with has too wide spaces, so make those half width
syl.width = math.floor(syl.width / 2)
syl.kind = "space"
elseif syl.kind == "kf" then
syl.kind = "deco"
elseif syl.kind == "k" then
syl.kind = "reg"
elseif syl.kind == "ko" then
syl.kind = "dance"
else
syl.kind = "ignore"
end
if syl.text == "#" then
syls[syls.n-1].duration = syls[syls.n-1].duration + syl.duration
syls[syls.n-1].end_time = syls[syls.n-1].end_time + syl.duration
else
linewidth = linewidth + syl.width
syls[syls.n] = syl
syls.n = syls.n + 1
end
end
local lineofs = math.floor((640 - linewidth) / 2)
for i = 0, syls.n-1 do
local syl = syls[i], copy_line(lin)
if syl.kind == "space" then
-- Spaces are skipped. Since the width of them is already incorporated in the position calculations
-- for the following syllables, they can safely be stripped from output, without losing the spaces.
elseif syl.kind == "ignore" then
-- These are actually syllable kinds we don't know what to do about. They are not included in the output.
else
local startx, starty, shakex, shakey, enterangle
-- angle to enter from
enterangle = math.rad((180 / lin.karaoke.n) * i - 90)
-- position to enter from (350 pixels from (320, 50))
startx = math.sin(enterangle)*350 + 320
starty = linetop - math.cos(enterangle)*350
if syl.kind == "reg" then
shakex = (syl.left+lineofs) + math.random(-5, 5)
shakey = linetop + math.random(-5, 5)
elseif syl.kind == "deco" then
shakex, shakey = syl.left+lineofs, linetop
elseif syl.kind == "dance" then
shakex = (syl.left+lineofs) + math.random(-5, 5)
shakey = linetop + math.random(-5, 5)
end
-- origin for rotation
local orgx = syl.left + lineofs + syl.width/2
local orgy = linetop + syl.height/2
-- entry effect
local enterlin = copy_line(lin)
enterlin.start_time = lin.start_time - 40
enterlin.end_time = lin.start_time
enterlin.text = string.format("{\\move(%d,%d,%d,%d)\\fr%d\\t(\\fr0)\\an7}%s", startx, starty, shakex, shakey, -math.deg(enterangle), syl.text)
table.insert(output, enterlin)
-- main highlight effect
local newlin = copy_line(lin)
local hilistart, hilimid, hiliend = syl.start_time*10, (syl.start_time+syl.duration/2)*10, (syl.start_time+syl.duration)*10
newlin.text = string.format("\\move(%d,%d,%d,%d,%d,%d)\\an7}%s", shakex, shakey, syl.left+lineofs, linetop, hilistart, hiliend, syl.text)
newlin.layer = 1
if syl.kind == "dance" then
local fx = string.format("\\org(%d,%d)\\t(%d,%d,\\fr30)\\t(%d,%d,\\fr-30)\\t(%d,%d,\\3c&H0000ff&)\\t(%d,%d,\\3c&H000080&)\\t(%d,%d,\\fr0)", orgx, orgy, hilistart, hilistart+20, hilistart+20, hiliend, hilistart, hilimid, hilimid, hiliend, hiliend, hiliend+syl.duration*10)
newlin.text = fx .. newlin.text
else
local fx = string.format("\\t(%d,%d,\\3c&H0000ff&)\\t(%d,%d,\\3c&H000080&)", hilistart, hilimid, hilimid, hiliend)
newlin.text = fx .. newlin.text
end
local bord = copy_line(newlin)
bord.layer = 0
bord.text = "{" .. bord.text
newlin.text = "{\\bord0" .. newlin.text
table.insert(output, bord)
table.insert(output, newlin)
-- leave effect
-- cut the line over in two, lower half "drops down", upper just fades away
local tophalf = copy_line(lin)
tophalf.start_time = lin.end_time
tophalf.end_time = lin.end_time + 100
tophalf.text = string.format("\\3c&H000080&\\fad(0,700)\\pos(%d,%d)\\an7}%s", syl.left+lineofs, linetop, syl.text_stripped)
local bottomhalf = copy_line(tophalf)
tophalf.text = string.format("{\\t(0,200,\\1c&H000080&)\\clip(0,0,640,%d)%s", linetop+syl.height/2, tophalf.text)
bottomhalf.text = string.format("{\\org(%d,%d)\\clip(0,%d,640,480)\\t(0,200,\\1c&H000080&)\\t(200,1000,1.2,\\frx90\\clip(0,%d,640,480)%s", 320, linetop+syl.height, linetop+syl.height/2, linetop+syl.height, bottomhalf.text)
table.insert(output, tophalf)
table.insert(output, bottomhalf)
end
end
end
function dokanji(lin, output, sty)
aegisub.set_status(lin.text_stripped)
local fontname = "@HGSHigemoji"
local lineheight = 0
local syltime = 0
local syls = {n=0}
for i = 1, lin.karaoke.n-1 do
local syl = lin.karaoke[i]
syl.height, syl.width, syl.descent, syl.extlead = aegisub.text_extents(sty, syl.text_stripped)
syl.start_time = syltime
syl.end_time = syltime + syl.duration
syltime = syltime + syl.duration
if syl.text == "#" then
syls[syls.n-1].duration = syls[syls.n-1].duration + syl.duration
syls[syls.n-1].end_time = syls[syls.n-1].end_time + syl.duration
--elseif syl.text == "" then
-- skip
else
lineheight = lineheight + syl.height
syls[syls.n] = syl
syls.n = syls.n + 1
end
end
for i = 0, syls.n-1 do
local syl = syls[i]
local top = 170
for j = 0, 8 do
if i+j >= syls.n then
break
end
local newlin = copy_line(lin)
newlin.start_time = lin.start_time + syl.start_time
newlin.end_time = newlin.start_time + syl.duration
local startalpha, targetalpha
if j == 0 then
startalpha = 0
targetalpha = 255
else
startalpha = j * 255 / 8
targetalpha = (j-1) * 255 / 8
end
newlin.text = string.format("{\\fn%s\\an7\\fr-90\\move(%d,%d,%d,%d)\\1a&H%2x&\\3a&H%2x&\\t(\\1a&H%2x&\\3a&H%2x&)}%s", fontname, 620, top, 620, top-syl.height, startalpha, startalpha, targetalpha, targetalpha, syls[i+j].text)
top = top + syls[i+j].height
table.insert(output, newlin)
end
end
end

View file

@ -0,0 +1,18 @@
-- Aegisub Automation demonstration script
-- Original written by Niels Martin Hansen
-- Given into the public domain
name = "Karaoke skeleton demo"
description = "This script demonstrates the use of the karaskel.lua include file, to avoid writing almost identical code for every karaoke effect script."
version, kind, configuration = 3, 'basic_ass', {}
include("karaskel.lua")
function do_syllable(meta, styles, config, line, syl)
if syl.i == 0 then
return syl.text
else
return string.format("{\\r\\k%d\\t(%d,%d,\\1c&H%s&)}%s", syl.duration, syl.start_time, syl.end_time, line.styleref.color2, syl.text)
end
end

View file

@ -0,0 +1,49 @@
-- Aegisub Automation demonstration script
-- Original written by Niels Martin Hansen
-- Given into the public domain
name = "Advanced skeleton demo"
description = "This script demonstrates using the karaskel-adv.lua include file to make rceation of per-syllable positioning effects easier."
version, kind, configuration = 3, 'basic_ass', {}
include("karaskel-adv.lua")
-- What kind of effect this makes:
-- Each syllable "jumps" up and down once during its duration
-- This is achieved using two \move operations, and as known, gabest's TextSub can only handle one \move per line
-- So we need two lines per syllable, split exactly in the middle of the duration of the syllable
function do_syllable(meta, styles, config, line, syl)
-- Make two copies of the original line (having the right timings etc)
local half1, half2 = copy_line(line), copy_line(line)
-- Make the first half line end halfway into the duration of the syllable
half1.end_time = half1.start_time + syl.start_time/10 + syl.duration/2
-- And make the second half line start where the first one ends
half2.start_time = half1.end_time
-- Where to move the syllable to/from
local fromx, fromy = line.centerleft+syl.center, line.height*2 + 20
local tox, toy = fromx, fromy - 10
-- Generate some text for the syllable
half1.text = string.format("{\\an8\\move(%d,%d,%d,%d,%d,%d)}%s", fromx, fromy, tox, toy, syl.start_time, syl.start_time+syl.duration*5, syl.text_stripped)
half2.text = string.format("{\\an8\\move(%d,%d,%d,%d,%d,%d)}%s", tox, toy, fromx, fromy, 0, syl.duration*5, syl.text_stripped)
-- Things will look bad with overlapping borders and stuff unless
-- we manually layer borders lower than text,
-- and shadows lower than borders, so let's do that
local half1b, half1s = copy_line(half1), copy_line(half1)
half1b.text = "{\\1a&HFF&\\shad0}" .. half1b.text
half1s.text = "{\\1a&HFF&\\bord0}" .. half1s.text
half1.text = "{\\bord0\\shad0}" .. half1.text
half1.layer = 2
half1b.layer = 1
half1s.layer = 0
local half2b, half2s = copy_line(half2), copy_line(half2)
half2b.text = "{\\1a&HFF&\\shad0}" .. half2b.text
half2s.text = "{\\1a&HFF&\\bord0\\shad2}" .. half2s.text
half2.text = "{\\bord0\\shad0}" .. half2.text
half2.layer = 2
half2b.layer = 1
half2s.layer = 0
-- Done, return the two new lines
return {n=6, [1]=half1, [2]=half2b, [3]=half1s, [4]=half2, [5]=half2b, [6]=half2s}
end

View file

@ -0,0 +1,18 @@
This directory contains various Aegisub Automation scripts provided for demonstration
purposes. Most of these were originally written for testing the various functions
in Automation durings its development, but they hopefully can also serve as good
places to learn how to do things.
All of these scripts are written by Niels Martin Hansen.
They are given into the public domain, but if you use a substantial part of any of
the more advanced ones, I'd really like a bit of credit where it's due.
And remember, it's cheap to use someone else's advanced karaoke effect, especially
without any changes at all. It also gives bad karma. Like, *really* bad :)
WARNING!
DO NOT ADD YOUR OWN FILES TO THIS DIRECTORY,
they will be deleted when you uninstall/upgrade Aegisub.
This also means it's unwise to modify these files, if you want to keep your
changes to them.

View file

@ -0,0 +1,158 @@
--[[
Copyright (c) 2005, Niels Martin Hansen
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Aegisub Group nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
]]
-- Aegisub Automation factory-brewed script
-- "Line-per-syllable effects"
-- Use karaskel.lua for skeleton code
include("karaskel-adv.lua")
-- Define the name of the script
name = "Line-per-syllable effects"
-- Define a description of the script
description = "Makes line-per-syllable effects, that is, each syllable in the script gets its own line. This allows for more advanced effects, such as syllables moving about separately, or overlapping each other."
-- Define the script variables that can be configured graphically
-- This is the primary point of the script, being able to configure it graphically
configuration = {
-- First a label to descript what special variables can be used
[1] = {
name = "label1";
kind = "label";
label = [[Variable-names are prefixed with $,
expressions are enclosed in % pairs.
Variables:
$START = Start-time of syllable (ms)
$END = End-time of syllable (ms)
$MID = Time midways through the syllable (ms)
$DUR = Duration of syllable (cs)
$X = X-position of syllable (center)
$WIDTH = width of syllable
$HEIGHT = height of syllable
(There is no $Y)
Calculation example:
\t($start,%$start+10,\fscx110\fscy110)
\move($x,40,%$x-$width+10%,40)
Remember you should always have a \pos or \move in your effect.]];
hint = ""
-- No "default", since a label doesn't have a value
},
-- Then a text field to input the string to replace \k's with
-- Make the default a "NOP" string
[2] = {
name = "k_repstr";
kind = "text";
label = "Effect";
hint = "The string to place at the start of every syllable line.";
default = "{\\an5\\pos($X,40)\\t($START,$END,\\fry360)}"
},
-- Allow the user to specify whether to strip tags or not
[3] = {
name = "striptags";
kind = "bool";
label = "Strip all tags";
hint = "Strip all formatting tags apart from the processed karaoke tags?";
default = false
},
[4] = {
name = "workstyle";
kind = "style";
label = "Line style";
hint = "Only apply the effect to lines with this style. Empty means apply to all lines.";
default = ""
}
}
-- Mandatory values
version, kind= 3, 'basic_ass'
function do_syllable(meta, styles, config, line, syl)
-- text is the replacement text for the syllable
-- ktext is the karaoke effect string
local text, ktext
-- Prepare the stripped or unstripped main text
if config.striptags then
text = syl.text_stripped
else
text = syl.text
end
-- Don't bother with empty syllables
if syl.text == "" then
return { n=0 }
end
-- Add the variable names to the syllable data
syl["dur"] = syl.duration
syl["start"] = syl.start_time
syl["end"] = syl.end_time
syl["mid"] = syl.start_time + syl.duration*5
syl["x"] = syl.center + line.centerleft
-- Prepare the karaoke effect string
ktext = config.k_repstr
-- Function for replacing the variables
local function var_replacer(varname)
varname = string.lower(varname)
if syl[varname] ~= nil then
return syl[varname]
else
aegisub.output_debug(string.format("Unknown variable name: %s", varname))
return "$" .. varname
end
end
-- Replace the variables in the ktext
ktext = string.gsub(ktext, "$(%a+)", var_replacer)
-- Function for evaluating expressions
local function expression_evaluator(expression)
chunk, err = loadstring(string.format("return (%s)", expression))
if (err) ~= nil then
aegisub.output_debug(string.format("Error parsing expression:\n%s", expression, err))
return "%" .. expression .. "%"
else
return chunk()
end
end
-- Find and evaluate expressions
ktext = string.gsub(ktext, "%%([^%%]*)%%", expression_evaluator)
local newline = copy_line(line)
newline.text = ktext .. text
return { n=1, [1]=newline }
end
function do_line(meta, styles, config, line)
if config.workstyle == "" or config.workstyle == line.style then
return adv_do_line(meta, styles, config, line)
else
return { n=1, [1]=line }
end
end

View file

@ -0,0 +1,12 @@
This directory contains flexible, configurable Automation scripts to do common jobs,
without having to do programming.
You can load these scripts into an ASS file in Aegisub, and apply an effect without
doing more than writing ASS override tags.
WARNING!
DO NOT ADD YOUR OWN FILES TO THIS DIRECTORY,
they will be deleted when you uninstall/upgrade Aegisub.
This also means it's unwise to modify these files, if you want to keep your
changes to them.

View file

@ -0,0 +1,143 @@
--[[
Copyright (c) 2005, Niels Martin Hansen
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Aegisub Group nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
]]
-- Aegisub Automation factory-brewed script
-- "Basic \k replacer"
-- Use karaskel.lua for skeleton code
include("karaskel.lua")
-- Define the name of the script
name = "Basic \\k replacer"
-- Define a description of the script
description = "Makes basic karaoke effects. Replace all \\k tags (and variations) with a custom string, where some variables can be substituted in."
-- Define the script variables that can be configured graphically
-- This is the primary point of the script, being able to configure it graphically
configuration = {
-- First a label to descript what special variables can be used
[1] = {
name = "label1";
kind = "label";
label = [[Variable-names are prefixed with $,
expressions are enclosed in % pairs.
Variables:
$START = Start-time of syllable (ms)
$END = End-time of syllable (ms)
$MID = Time midways through the syllable (ms)
$DUR = Duration of syllable (cs)
Calculation example:
\t($start,%$start+$dur*2%,\fscx110)
\t(%$start+$dur*2%,$end,\fscx90)]];
hint = ""
-- No "default", since a label doesn't have a value
},
-- Then a text field to input the string to replace \k's with
-- Make the default a "NOP" string
[2] = {
name = "k_repstr";
kind = "text";
label = "\\k replacement";
hint = "The string to replace \\k tags with. Should start and end with { } characters.";
default = "{\\k$DUR}"
},
-- Allow the user to specify whether to strip tags or not
[3] = {
name = "striptags";
kind = "bool";
label = "Strip all tags";
hint = "Strip all formatting tags apart from the processed karaoke tags?";
default = false
},
[4] = {
name = "workstyle";
kind = "style";
label = "Line style";
hint = "Only apply the effect to lines with this style. Empty means apply to all lines.";
default = ""
}
}
-- Mandatory values
version, kind= 3, 'basic_ass'
function do_syllable(meta, styles, config, line, syl)
-- text is the replacement text for the syllable
-- ktext is the karaoke effect string
local text, ktext
-- Prepare the stripped or unstripped main text
if config.striptags then
text = syl.text_stripped
else
text = syl.text
end
-- Add the variable names to the syllable data
syl["dur"] = syl.duration
syl["start"] = syl.start_time
syl["end"] = syl.end_time
syl["mid"] = syl.start_time + syl.duration*5
ktext = config.k_repstr
-- Function for replacing the variables
local function var_replacer(varname)
varname = string.lower(varname)
if syl[varname] ~= nil then
return syl[varname]
else
aegisub.output_debug(string.format("Unknown variable name: %s", varname))
return "$" .. varname
end
end
-- Replace the variables in the ktext
ktext = string.gsub(ktext, "$(%a+)", var_replacer)
-- Function for evaluating expressions
local function expression_evaluator(expression)
chunk, err = loadstring(string.format("return (%s)", expression))
if (err) ~= nil then
aegisub.output_debug(string.format("Error parsing expression:\n%s", expression, err))
return "%" .. expression .. "%"
else
return chunk()
end
end
-- Find and evaluate expressions
ktext = string.gsub(ktext, "%%([^%%]*)%%", expression_evaluator)
return ktext .. text
end
function do_line(meta, styles, config, line)
if config.workstyle == "" or config.workstyle == line.style then
return default_do_line(meta, styles, config, line)
else
return { n=1, [1]=line }
end
end

View file

@ -0,0 +1,64 @@
--[[
Copyright (c) 2005, Niels Martin Hansen
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Aegisub Group nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
]]
-- Aegisub Automation include file
-- This include file is an extension of karaskel.lua providing out-of-the-box support for per-syllable
-- positioning of karaoke.
-- It automatically includes and re-setups karaskel.lua, so you should not include that yourself!
include("karaskel.lua")
-- Also include utils.lua, since you'll most likely need to use the copy_line function
include("utils.lua")
-- The interface here has been greatly simplified, there is only one function to override, do_syllable
-- The format for that one has changed.
-- The rest of the functions can be "emulated" through the do_syllable function.
-- Though you can still also override do_line and get an effect our of it, it's not advisable, since that
-- defeats the entire purpose of using this include file.
-- The arguments here mean the same as in karaskel.lua, and all tables have the same members
-- The return value is different now, though.
-- It is required to be in the same format as the do_line function:
-- A table with an "n" key, and keys 0..n-1 with line structures.
function default_do_syllable(meta, styles, config, line, syl)
return {n=0}
end
do_syllable = default_do_syllable
function adv_do_line(meta, styles, config, line)
local result = {n=0}
for i = 0, line.karaoke.n-1 do
local out = do_syllable(meta, styles, config, line, line.karaoke[i])
for j = 1, out.n do
table.insert(result, out[j])
end
end
return result
end
do_line = adv_do_line

View file

@ -0,0 +1,183 @@
--[[
Copyright (c) 2005, Niels Martin Hansen
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Aegisub Group nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT,