dotfiles/.aegisub/automation/include/lyger/LibLyger.moon

362 lines
13 KiB
Plaintext

[[
README
This file is a library of commonly used functions across all my automation
scripts. This way, if there are errors or updates for any of these functions,
I'll only need to update one file.
The filename is a bit vain, perhaps, but I couldn't come up with anything else.
]]
DependencyControl = require("l0.DependencyControl")
version = DependencyControl{
name: "LibLyger",
version: "2.0.3",
description: "Library of commonly used functions across all of lyger's automation scripts.",
author: "lyger",
url: "http://github.com/TypesettingTools/lyger-Aegisub-Scripts",
moduleName: "lyger.LibLyger",
feed: "https://raw.githubusercontent.com/TypesettingTools/lyger-Aegisub-Scripts/master/DependencyControl.json",
{
"aegisub.util", "karaskel"
}
}
util = version\requireModules!
class LibLyger
msgs = {
preproc_lines: {
bad_type: "Error: argument #1 must be either a line object, an index into the subtitle object or a table of indexes; got a %s."
}
}
new: (sub, sel, generate_furigana) =>
@set_sub sub, sel, generate_furigana if sub
set_sub: (@sub, @sel = {}, generate_furigana = false) =>
@script_info, @lines, @dialogue, @dlg_cnt = {}, {}, {}, 0
for i, line in ipairs sub
@lines[i] = line
switch line.class
when "info" then @script_info[line.key] = line.value
when "dialogue"
@dlg_cnt += 1
@dialogue[@dlg_cnt], line.i = line, i
@meta, @styles = karaskel.collect_head @sub, generate_furigana
@preproc_lines @sel
insert_line: (line, i = #@lines + 1) =>
table.insert(@lines, i, line)
@sub.insert(i, line)
preproc_lines: (lines) =>
val_type = type lines
-- indexes into the subtitles object
if val_type == "number"
lines, val_type = {@lines[lines]}, "table"
assert val_type == "table", msgs.preproc_lines.bad_type\format val_type
-- line objects
if lines.raw and lines.section and not lines.duration
karaskel.preproc_line @sub, @meta, @styles, lines
-- tables of line numbers/objects such as the selection
else @preproc_lines line for line in *lines
-- returns a "Lua" portable version of the string
exportstring: (s) -> string.format "%q", s
--Lookup table for the nature of each kind of parameter
param_type: {
alpha: "alpha"
"1a": "alpha"
"2a": "alpha"
"3a": "alpha"
"4a": "alpha"
c: "color"
"1c": "color"
"2c": "color"
"3c": "color"
"4c": "color"
fscx: "number"
fscy: "number"
frz: "angle"
frx: "angle"
fry: "angle"
shad: "number"
bord: "number"
fsp: "number"
fs: "number"
fax: "number"
fay: "number"
blur: "number"
be: "number"
xbord: "number"
ybord: "number"
xshad: "number"
yshad: "number"
pos: "point"
org: "point"
clip: "clip"
}
--Convert float to neatly formatted string
float2str: (f) -> "%.3f"\format(f)\gsub("%.(%d-)0+$","%.%1")\gsub "%.$", ""
--Escapes string for use in gsub
esc: (str) -> str\gsub "([%%%(%)%[%]%.%*%-%+%?%$%^])","%%%1"
[[
Tags that can have any character after the tag declaration: \r, \fn
Otherwise, the first character after the tag declaration must be:
a number, decimal point, open parentheses, minus sign, or ampersand
]]
-- Remove listed tags from the given text
line_exclude: (text, exclude) ->
remove_t = false
new_text = text\gsub "\\([^\\{}]*)", (a) ->
if a\match "^r"
for val in *exclude
return "" if val == "r"
elseif a\match "^fn"
for val in *exclude
return "" if val == "fn"
else
tag = a\match "^[1-4]?%a+"
for val in *exclude
if val == tag
--Hacky exception handling for \t statements
if val == "t"
remove_t = true
return "\\#{a}"
elseif a\match "%)$"
return a\match("%b()") and "" or ")"
else
return ""
return "\\"..a
if remove_t
new_text = new_text\gsub "\\t%b()", ""
return new_text\gsub "{}", ""
-- Remove all tags except the given ones
line_exclude_except: (text, exclude) ->
remove_t = true
new_text = text\gsub "\\([^\\{}]*)", (a) ->
if a\match "^r"
for val in *exclude
return "\\#{a}" if val == "r"
elseif a\match "^fn"
for val in *exclude
return "\\#{a}" if val == "fn"
else
tag = a\match "^[1-4]?%a+"
for val in *exclude
if val == tag
remove_t = false if val == "t"
return "\\#{a}"
if a\match "^t"
return "\\#{a}"
elseif a\match "%)$"
return a\match("%b()") and "" or ")"
else return ""
if remove_t
new_text = new_text\gsub "\\t%b()", ""
return new_text
-- Returns the position of a line
get_default_pos: (line, align_x, align_y) =>
@preproc_lines line
x = {
@script_info.PlayResX - line.eff_margin_r,
line.eff_margin_l,
line.eff_margin_l + (@script_info.PlayResX - line.eff_margin_l - line.eff_margin_r) / 2
}
y = {
@script_info.PlayResY - line.eff_margin_b,
@script_info.PlayResY / 2
line.eff_margin_t
}
return x[align_x], y[align_y]
get_pos: (line) =>
posx, posy = line.text\match "\\pos%(([%d%.%-]*),([%d%.%-]*)%)"
unless posx
posx, posy = line.text\match "\\move%(([%d%.%-]*),([%d%.%-]*),"
return tonumber(posx), tonumber(posy) if posx
-- \an alignment
if align = tonumber line.text\match "\\an([%d%.%-]+)"
return @get_default_pos line, align%3 + 1, math.ceil align/3
-- \a alignment
elseif align = tonumber line.text\match "\\a([%d%.%-]+)"
return @get_default_pos line, align%4,
align > 8 and 2 or align> 4 and 3 or 1
-- no alignment tags (take karaskel values)
else return line.x, line.y
-- Returns the origin of a line
get_org: (line) =>
orgx, orgy = line.text\match "\\org%(([%d%.%-]*),([%d%.%-]*)%)"
if orgx
return orgx, orgy
else return @get_pos line
-- Returns a table of default values
style_lookup: (line) =>
@preproc_lines line
return {
alpha: "&H00&"
"1a": util.alpha_from_style line.styleref.color1
"2a": util.alpha_from_style line.styleref.color2
"3a": util.alpha_from_style line.styleref.color3
"4a": util.alpha_from_style line.styleref.color4
c: util.color_from_style line.styleref.color1
"1c": util.color_from_style line.styleref.color1
"2c": util.color_from_style line.styleref.color2
"3c": util.color_from_style line.styleref.color3
"4c": util.color_from_style line.styleref.color4
fscx: line.styleref.scale_x
fscy: line.styleref.scale_y
frz: line.styleref.angle
frx: 0
fry: 0
shad: line.styleref.shadow
bord: line.styleref.outline
fsp: line.styleref.spacing
fs: line.styleref.fontsize
fax: 0
fay: 0
xbord: line.styleref.outline
ybord: line.styleref.outline
xshad: line.styleref.shadow
yshad: line.styleref.shadow
blur: 0
be: 0
}
-- Modify the line tables so they are split at the same locations
match_splits: (line_table1, line_table2) ->
for i=1, #line_table1
text1 = line_table1[i].text
text2 = line_table2[i].text
insert = (target, text, i) ->
for j = #target, i+1, -1
target[j+1] = target[j]
target[i+1] = tag: "{}", text: target[i].text\match "#{LibLyger.esc(text)}(.*)"
target[i] = tag: target[i].tag, :text
if #text1 > #text2
-- If the table1 item has longer text, break it in two based on the text of table2
insert line_table1, text2, i
elseif #text2 > #text1
-- If the table2 item has longer text, break it in two based on the text of table1
insert line_table2, text1, i
return line_table1, line_table2
-- Remove listed tags from any \t functions in the text
time_exclude: (text, exclude) ->
text = text\gsub "(\\t%b())", (a) ->
b = a
for tag in *exclude
if a\match "\\#{tag}"
b = b\gsub(tag == "clip" and "\\#{tag}%b()" or "\\#{tag}[^\\%)]*", "")
return b
-- get rid of empty blocks
return text\gsub "\\t%([%-%.%d,]*%)", ""
-- Returns a state table, restricted by the tags given in "tag_table"
-- WILL NOT WORK FOR \fn AND \r
make_state_table: (line_table, tag_table) ->
this_state_table = {}
for i, val in ipairs line_table
temp_line_table = {}
pstate = LibLyger.line_exclude_except val.tag, tag_table
for j, ctag in ipairs tag_table
-- param MUST start in a non-alpha character, because ctag will never be \r or \fn
-- If it is, you fucked up
param = pstate\match "\\#{ctag}(%A[^\\{}]*)"
temp_line_table[ctag] = param if param
this_state_table[i] = temp_line_table
return this_state_table
interpolate: (this_table, start_state_table, end_state_table, factor, preset) ->
this_current_state = {}
rebuilt_text = for k, val in ipairs this_table
temp_tag = val.tag
-- Cycle through all the tag blocks and interpolate
for ctag, param in pairs start_state_table[k]
temp_tag = "{}" if #temp_tag == 0
temp_tag = temp_tag\gsub "}", ->
tval_start, tval_end = start_state_table[k][ctag], end_state_table[k][ctag]
tag_type = LibLyger.param_type[ctag]
ivalue = switch tag_type
when "alpha"
util.interpolate_alpha factor, tval_start, tval_end
when "color"
util.interpolate_color factor, tval_start, tval_end
when "number", "angle"
nstart, nend = tonumber(tval_start), tonumber(tval_end)
if tag_type == "angle" and preset.c.flip_rot
nstart %= 360
nend %= 360
ndelta = nend - nstart
if 180 < math.abs ndelta
nstart += ndelta * 360 / math.abs ndelta
nvalue = util.interpolate factor, nstart, nend
nvalue += 360 if tag_type == "angle" and nvalue < 0
LibLyger.float2str nvalue
when "point", "clip" then nil -- not touched by this function
else ""
-- check for redundancy
if this_current_state[ctag] == ivalue
return "}"
this_current_state[ctag] = ivalue
return "\\#{ctag..ivalue}}"
temp_tag .. val.text
return table.concat(rebuilt_text)\gsub "{}", ""
write_table: (my_table, file, indent) ->
indent or= ""
charS, charE = " ", "\n"
--Opening brace of the table
file\write "#{indent}{#{charE}"
for key,val in pairs my_table
file\write switch type key
when "number" then indent..charS
when "string" then table.concat {indent, charS, "[", LibLyger.exportstring(key), "]="}
else "#{indent}#{charS}#{key}="
switch type val
when "table"
file\write charE
LibLyger.write_table val, file, indent..charS
file\write indent..charS
when "string" then file\write LibLyger.exportstring val
when "number" then file\write tostring val
when "boolean" then file\write val and "true" or "false"
file\write ","..charE
-- Closing brace of the table
file\write "#{indent}}#{charE}"
:version
return version\register LibLyger