Compare commits

...

No commits in common. "master" and "setup" have entirely different histories.

720 changed files with 14 additions and 73977 deletions

View file

@ -1 +0,0 @@
export AEGISUB_LUA=1

@ -1 +0,0 @@
Subproject commit be2957e58b7ebdc6d3522a792f0120798dfbafa7

View file

@ -1,75 +0,0 @@
export script_name = 'Selegator'
export script_description = 'Select/navigate in the subtitle grid'
export script_author = 'tophf'
export script_version = '1.1.5'
export script_namespace = 'Flux.Selegator'
DependencyControl = require('l0.DependencyControl') {
url: 'https://github.com/TypesettingTools/CoffeeFlux-Aegisub-Scripts/blob/master/macros/Flux.Selegator.moon'
feed: 'https://raw.githubusercontent.com/TypesettingTools/CoffeeFlux-Aegisub-Scripts/master/DependencyControl.json'
{}
}
selectAll = (subs, sel, act) ->
lookforstyle = subs[act].style
if #sel>1
[i for i in *sel when subs[i].style==lookforstyle]
else
[k for k,s in ipairs subs when s.style==lookforstyle]
findPrevious = (subs, sel, act) ->
lookforstyle = subs[act].style
for i = act-1,1,-1
return if subs[i].class!='dialogue'
if subs[i].style==lookforstyle
return {i}
findNext = (subs, sel, act) ->
lookforstyle = subs[act].style
for i = act+1,#subs
if subs[i].style==lookforstyle
return {i}
firstInBlock = (subs, sel, act) ->
lookforstyle = subs[act].style
for i = act-1,1,-1
if subs[i].class!='dialogue' or subs[i].style!=lookforstyle
return {i+1}
lastInBlock = (subs, sel, act) ->
lookforstyle = subs[act].style
for i = act+1,#subs
if subs[i].style!=lookforstyle
return {i-1}
{#subs}
selectBlock = (subs, sel, act) ->
lookforstyle = subs[act].style
first, last = act, #subs
for i = act-1,1,-1
if subs[i].class!='dialogue' or subs[i].style!=lookforstyle
first = i + 1
break
for i = act+1,#subs
if subs[i].class!='dialogue' or subs[i].style!=lookforstyle
last = i - 1
break
[i for i=first,last]
untilStart = (subs, sel, act) ->
[i for i = 1,act when subs[i].class=='dialogue']
untilEnd = (subs, sel, act) ->
[i for i = act,#subs when subs[i].class=='dialogue']
DependencyControl\registerMacros {
{ 'Current Style/Select All', '', selectAll }
{ 'Current Style/Previous', '', findPrevious }
{ 'Current Style/Next', '', findNext }
{ 'Current Style/First In Block', '', firstInBlock }
{ 'Current Style/Last In Block', '', lastInBlock }
{ 'Current Style/Select Block', '', selectBlock }
{ 'Select Until Start', '', untilStart }
{ 'Select Until End', '', untilEnd }
}

File diff suppressed because it is too large Load diff

View file

@ -1,25 +0,0 @@
local tr = aegisub.gettext
script_name = tr"Clean k tags"
script_description = tr"Remove double k tags"
script_author = "amoethyst"
script_version = "1.0"
function special_k(subs, sel)
-- if the first tag is K/kf this would break the timing for the previous timing
local expr = "^(.-){\\(ko?)([0-9.]*)[^}]-}([^{]-){\\[kK][fo]?([0-9.]*)[^}]-}( -{(\\[kK][fo]?)[0-9.]*[^}]-}.*)$"
for _, i in ipairs(sel) do
line = subs[i]
before, tag, k1, between, k2, after = line.text:match(expr)
while after ~= nil do
line.text = before .. "{\\" .. tag .. tonumber(k1) + tonumber(k2) .. "}" .. between .. after
subs[i] = line
before, tag, k1, between, k2, after = line.text:match(expr)
end
end
end
aegisub.register_macro(script_name, script_description, special_k)

View file

@ -1,178 +0,0 @@
local tr = aegisub.gettext
script_name = tr"Duetto Meika"
script_description = tr"The ultimate tool for karaoke duets"
script_author = "amoethyst"
include("utils.lua")
function replace_style(line, style_name, style_string)
before_style, after_style = line.text:match("^(.-{[^}]-)\\?s:".. style_name .."(.*)$")
return before_style .. style_string .. after_style
end
function duetto(subs, sel)
styles = {}
-- create the style map
for _, line in ipairs(subs) do
if line.class == "style" then
styles[line.name] = line
end
end
-- duetto~
for _, i in ipairs(sel) do
line = subs[i]
current_style = styles[line.style]
-- match every `s:` marker
for style_name in line.text:gmatch("{[^}]*s:([^}\\]*)[^}]*}") do
if style_name ~= current_style.name then
style = styles[style_name]
-- build the tags to use the new style
style_string = ""
if current_style.color1 ~= style.color1 then
style_string = style_string .. "\\c" .. style.color1
end
if current_style.color2 ~= style.color2 then
style_string = style_string .. "\\2c" .. style.color2
end
if current_style.color3 ~= style.color3 then
style_string = style_string .. "\\3c" .. style.color3
end
if current_style.color4 ~= style.color4 then
style_string = style_string .. "\\4c" .. style.color4
end
-- set style
line.text = replace_style(line, style_name, style_string)
current_style = style
else
-- remove marker to not break everything
line.text = replace_style(line, style_name, "")
end
end
subs[i] = line
end
aegisub.set_undo_point(script_name)
end
function test_colors(c1, c2)
return color_from_style(c1) == color_from_style(c2)
end
function get_script_style(style, styles)
for key, script_style in pairs(styles) do
if (test_colors(style.color1, script_style.color1)
and test_colors(style.color2, script_style.color2)
and test_colors(style.color3, script_style.color3)
and test_colors(style.color4, script_style.color4)
and tonumber(style.fontsize) == tonumber(script_style.fontsize)
and style.fontname == script_style.fontname) then
return script_style
end
end
return nil
end
function deduetto_meika(subs, sel)
local styles = {}
local last_style = -1
-- create the style map
for i, line in ipairs(subs) do
if line.class == "style" then
styles[line.name] = line
last_style = i
end
end
local new_styles = {}
for _, i in ipairs(sel) do
local line = subs[i]
local current_style = table.copy(styles[line.style])
local search_index = 1
while search_index < #line.text do
local match_start, match_end = line.text:find("{[^}]*}", search_index)
if match_start == nil then
break
end
local bracketed = line.text:sub(match_start, match_end)
local new_style = false
-- change style's colors
for tag, value in bracketed:gmatch("\\([1-4]?c)([^}\\]*)") do
new_style = true
if tag == "c" or tag == "1c" then
current_style.color1 = value
elseif tag == "2c" then
current_style.color2 = value
elseif tag == "3c" then
current_style.color3 = value
elseif tag == "4c" then
current_style.color4 = value
end
end
-- change style's font
for tag, value in bracketed:gmatch("\\(f[sn])([^}\\]*)") do
new_style = true
if tag == "fs" then
current_style.fontsize = value
elseif tag == "fn" then
current_style.fontname = value
end
end
if new_style then
aegisub.log("oof\n")
local script_style = get_script_style(current_style, styles)
if script_style == nil then
aegisub.log("foo\n")
if get_script_style(current_style, new_styles) == nil then
new_styles[#new_styles+1] = table.copy(current_style)
end
else
aegisub.log("ofo\n")
-- remove inline colors
bracketed = bracketed:gsub("\\[1-4]?c[^\\}]*", "")
bracketed = bracketed:gsub("\\[1-4]?a[^\\}]*", "")
-- remove inline fonts
bracketed = bracketed:gsub("\\f[sn][^\\}]*", "")
-- add style marker
bracketed = "{s:" .. script_style.name .. bracketed:sub(2, #bracketed)
line.text = line.text:sub(1, match_start-1) .. bracketed .. line.text:sub(match_end + 1, #line.text)
end
end
search_index = match_start + 1
end
subs[i] = line
end
if #new_styles > 0 then
for i, new_style in ipairs(new_styles) do
new_style.name = "Deduetto style " .. i
subs.insert(last_style, new_style)
last_style = last_style + 1
aegisub.log("Created new style: " .. new_style.name .. "\n")
end
end
end
aegisub.register_macro(script_name, script_description, duetto)
aegisub.register_macro(tr"Deduetto Meika", tr"Create styles from inline color tags", deduetto_meika)

View file

@ -1,24 +0,0 @@
local tr = aegisub.gettext
script_name = tr"frz endro"
script_description = tr"probably won't be useful anytime soon"
script_author = "amoethyst"
script_version = "1.0"
function frz_combo(subs, sel)
local expr = "frz([0-9.-]+)"
for _, i in ipairs(sel) do
line = subs[i]
aegisub.log(line.text)
frz_text = line.text:match(expr)
aegisub.log(frz_text)
frz = tonumber(frz_text) * -1
line.text = line.text .. "{\\frz"..frz.."}"
subs[i] = line
end
end
aegisub.register_macro(script_name, script_description, frz_combo)

View file

@ -1,235 +0,0 @@
--[[
==README==
Circular Text
Define an origin, and this will put the text on a circular arc centered on that origin.
An origin must be defined and it must be different from the position of the line. You can't have
a circle if the radius is zero.
The x coordinate of the position tag should match the x coordinate of the origin tag for best
results. In other words, your original line should be at a right angle to the radius. Note that
these are the x coordinates in the tags, not the x coordinates on screen, which will change if
you rotate the tag.
Supports varied fonts, font sizes, font spacings, and x/y scales in the same line.
The resulting arc will be centered on the original rotation of your line.
Only works on static lines. If you want the line to move or rotate, use another macro.
]]--
script_name = "Circular text"
script_description = "Puts the text on a circular arc centered on the origin."
script_version = "0.2.0"
script_author = "lyger"
script_namespace = "lyger.CircleText"
local DependencyControl = require("l0.DependencyControl")
local rec = DependencyControl{
feed = "https://raw.githubusercontent.com/TypesettingTools/lyger-Aegisub-Scripts/master/DependencyControl.json",
{
{"lyger.LibLyger", version = "2.0.0", url = "http://github.com/TypesettingTools/lyger-Aegisub-Scripts"},
"aegisub.util"
}
}
local LibLyger, util = rec:requireModules()
local libLyger = LibLyger()
--[[
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
]]--
--Distance between two points
local function distance(x1,y1,x2,y2)
return math.sqrt((x2-x1)^2+(y2-y1)^2)
end
--Sign of a value
local function sign(n)
return n/math.abs(n)
end
--Angle in degrees, given the arc length and radius
local function arc_angle(arc_length,radius)
return arc_length/radius * 180/math.pi
end
--Main processing function
function circle_text(sub,sel)
libLyger:set_sub(sub, sel)
for si,li in ipairs(sel) do
--Progress report
aegisub.progress.task("Processing line "..si.."/"..#sel)
aegisub.progress.set(100*si/#sel)
--Read in the line
line = libLyger.lines[li]
--Get position and origin
px, py = libLyger:get_pos(line)
ox, oy = libLyger:get_org(line)
--Make sure pos and org are not the same
if px==ox and py==oy then
aegisub.log(1,"Error on line %d: Position and origin cannot be the same!",li)
return
end
--Get radius
radius=distance(px,py,ox,oy)
--Remove \pos and \move
--If your line was non-static, too bad
line.text = LibLyger.line_exclude(line.text,{"pos","move"})
--Make sure line starts with a tag block
if line.text:find("^{")==nil then
line.text="{}"..line.text
end
--Rotation direction: positive if each character adds to the angle,
--negative if each character subtracts from the angle
rot_dir=sign(py-oy)
--Add the \pos back with recalculated position
line.text=line.text:gsub("^{",string.format("{\\pos(%d,%d)",ox,oy+rot_dir*radius))
--Get z rotation
--Will only take the first one, because if you wanted the text to be on a circular arc,
--why do you have more than one z rotation tag in the first place?
_,_,zrot=line.text:find("\\frz([%-%.%d]+)")
zrot=zrot or line.styleref.angle
--Make line table
line_table={}
for thistag,thistext in line.text:gmatch("({[^{}]*})([^{}]*)") do
table.insert(line_table,{tag=thistag,text=thistext})
end
--Where data on the character widths will be stored
char_data={}
--Total width of line
cum_width=0
--Stores current state of the line as style table
current_style = util.deep_copy(line.styleref)
--First pass to collect data on character widths
for i,val in ipairs(line_table) do
char_data[i]={}
--Fix style tables to reflect override tags
local _,_,font_name=val.tag:find("\\fn([^\\{}]+)")
local _,_,font_size=val.tag:find("\\fs([%-%.%d]+)")
local _,_,font_scx=val.tag:find("\\fscx([%-%.%d]+)")
local _,_,font_scy=val.tag:find("\\fscy([%-%.%d]+)")
local _,_,font_sp=val.tag:find("\\fsp([%-%.%d]+)")
local _,_,_bold=val.tag:find("\\b([01])")
local _,_,_italic=val.tag:find("\\i([01])")
current_style.fontname=font_name or current_style.fontname
current_style.fontsize=tonumber(font_size) or current_style.fontsize
current_style.scale_x=tonumber(font_scx) or current_style.scale_x
current_style.scale_y=tonumber(font_scy) or current_style.scale_y
current_style.spacing=tonumber(font_sp) or current_style.spacing
if _bold~=nil then
if _bold=="1" then current_style.bold=true
else current_style.bold=false end
end
if _italic~=nil then
if _italic=="1" then current_style.italic=true
else current_style.italic=false end
end
val.style = util.deep_copy(current_style)
--Collect width data on each char
for thischar in val.text:gmatch(".") do
cwidth=aegisub.text_extents(val.style,thischar)
table.insert(char_data[i],{char=thischar,width=cwidth})
end
--Increment cumulative width
cum_width=cum_width+aegisub.text_extents(val.style,val.text)
end
--The angle that the rotation will begin at
start_angle=zrot-(rot_dir*arc_angle(cum_width,radius))/2
rebuilt_text=""
cum_rot=0
--Second pass to rebuild line with new tags
for i,val in ipairs(line_table) do
rebuilt_text=rebuilt_text..val.tag:gsub("\\fsp[%-%.%d]+",""):gsub("\\frz[%-%.%d]+","")
for k,tchar in ipairs(char_data[i]) do
--Character spacing should be the average of this character's width and the next one's
--For spacing, scale width back up by the character's relevant scale_x,
--because \fsp scales too. Also, subtract the existing font spacing
this_spacing=0
this_width=0
if k~=#char_data[i] then
this_width=(tchar.width+char_data[i][k+1].width)/2
this_spacing=-1*(this_width*100/val.style.scale_x-val.style.spacing)
else
this_width=i~=#line_table and (tchar.width+char_data[i+1][1].width)/2 or 0
this_spacing=i~=#line_table
and -1*((tchar.width*100/val.style.scale_x
+ char_data[i+1][1].width*100/line_table[i+1].style.scale_x)/2
-val.style.spacing)
or 0
end
rebuilt_text=rebuilt_text..string.format("{\\frz%.3f\\fsp%.2f}%s",
(start_angle+rot_dir*cum_rot)%360,this_spacing,tchar.char)
cum_rot=cum_rot+arc_angle(this_width,radius)
end
end
--[[
--Fuck the re library. Maybe I'll come back to this
whitespaces=re.find(rebuilt_text,
'(\{\\\\frz[\\d\\.\\-]+\\\\fsp[\\d\\.\\-]+\}\\S)((?:\{\\\\frz[\\d\\.\\-]+\\\\fsp[\\d\\.\\-]+\}\\s)+)')
for j=1,#whitespaces-1,2 do
first_tag=whitespaces[j].str
other_tags=whitespaces[j+1].str
aegisub.log("%s%s\n",first_tag,other_tags)
first_space=first_tag:match("\\fsp([%d%.%-]+)")
other_spaces=0
total_wsp=0
for _sp in other_tags:gmatch("\\fsp([%d%.%-]+)") do
other_spaces=other_spaces+tonumber(_sp)
total_wsp=total_wsp+1
end
total_space=tonumber(first_space)+other_spaces
rebuilt_text=rebuilt_text:gsub(first_tag..other_tags,
first_tag:gsub("\\fsp[%d%.%-]+",string.format("\\fsp%.2f",total_space))..string.rep(" ",total_wsp))
end]]--
line.text=rebuilt_text:gsub("}{","")
sub[li]=line
end
aegisub.set_undo_point(script_name)
end
rec:registerMacro(circle_text)

View file

@ -1,472 +0,0 @@
--[[
==README==
Blur clip
There's really not much to explain here. \clip statements produce a sharp edge. This script
draws new \clip statements with decreasing alphas in order to imitate the effect of a blur.
The appearance won't always be perfect because of the limitations of precision with vector
clip coordinates. The "precision" parameter ameliorates this somewhat, but the odd jagged
line here and there is inevitable.
A note on the "precision" parameter: it scales exponentionally. If you want a 5-pixel blur,
then a precision of 1 produces 6 lines (5 for the blur, 1 for the center). Precision 2 will
generate 11 lines (10 for the blur, 1 for the center) and precision 3 will generate 21 lines
(20 for the blur, 1 for the center). As you've probably figured out, a precision of 4 will
create a whopping 41 lines. Use with caution.
]]--
script_name = "Blur clip"
script_description = "Blurs a vector clip."
script_version = "1.2.0"
script_author = "lyger"
script_namespace = "lyger.ClipBlur"
local DependencyControl = require("l0.DependencyControl")
local rec = DependencyControl{
feed = "https://raw.githubusercontent.com/TypesettingTools/lyger-Aegisub-Scripts/master/DependencyControl.json",
{
{"lyger.LibLyger", version = "2.0.0", url = "http://github.com/TypesettingTools/lyger-Aegisub-Scripts"},
"aegisub.util"
}
}
local LibLyger, util = rec:requireModules()
local libLyger = LibLyger()
--Distance between two points
local function distance(x1,y1,x2,y2)
return math.sqrt((x2-x1)^2+(y2-y1)^2)
end
--Sign of a value
local function sign(n)
return n/math.abs(n)
end
--Haha I didn't know these functions existed. May as well just alias them
local todegree=math.deg
local torad=math.rad
--Parses vector shape and makes it into a table
function make_vector_table(vstring)
local vtable={}
local vexp=vstring:match("^([1-4]),")
vexp=tonumber(vexp) or 1
for vtype,vcoords in vstring:gmatch("([mlb])([%d%s%-]+)") do
for vx,vy in vcoords:gmatch("([%d%-]+)%s+([%d%-]+)") do
table.insert(vtable,{["class"]=vtype,["x"]=tonumber(vx),["y"]=tonumber(vy)})
end
end
return vtable,vexp
end
--Reverses a vector table object
function reverse_vector_table(vtable)
local nvtable={}
if #vtable<1 then return nvtable end
--Make sure vtable does not end in an m. I don't know why this would happen but still
maxi=#vtable
while vtable[maxi].class=="m" do
maxi=maxi-1
end
--All vector shapes start with m
nstart = util.copy(vtable[maxi])
tclass=nstart.class
nstart.class="m"
table.insert(nvtable,nstart)
--Reinsert coords in backwards order, but shift the class over by 1
--because that's how vector shapes behave in aegi
for i=maxi-1,1,-1 do
tcoord = util.copy(vtable[i])
_temp=tcoord.class
tcoord.class=tclass
tclass=_temp
table.insert(nvtable,tcoord)
end
return nvtable
end
--Turns vector table into string
function vtable_to_string(vt)
cclass=nil
result=""
for i=1,#vt,1 do
if vt[i].class~=cclass then
result=result..string.format("%s %d %d ",vt[i].class,vt[i].x,vt[i].y)
cclass=vt[i].class
else
result=result..string.format("%d %d ",vt[i].x,vt[i].y)
end
end
return result
end
--Rounds to the given number of decimal places
function round(n,dec)
dec=dec or 0
return math.floor(n*10^dec+0.5)/(10^dec)
end
--Grows vt outward by the radius r scaled by sc
function grow(vt,r,sc)
ch=get_chirality(vt)
local wvt=wrap(vt)
local nvt={}
sc=sc or 1
--Grow
for i=2,#wvt-1,1 do
cpt=wvt[i]
ppt=wvt[i].prev
npt=wvt[i].next
while distance(cpt.x,cpt.y,ppt.x,ppt.y)==0 do
ppt=ppt.prev
end
while distance(cpt.x,cpt.y,npt.x,npt.y)==0 do
npt=npt.prev
end
rot1=todegree(math.atan2(cpt.y-ppt.y,cpt.x-ppt.x))
rot2=todegree(math.atan2(npt.y-cpt.y,npt.x-cpt.x))
drot=(rot2-rot1)%360
--Angle to expand at
nrot=(0.5*drot+90)%180
if ch<0 then nrot=nrot+180 end
--Adjusted radius
__ar=math.cos(torad(ch*90-nrot)) --<3
ar=(__ar<0.00001 and r) or r/math.abs(__ar)
newx=cpt.x*sc
newy=cpt.y*sc
if r~=0 then
newx=newx+sc*round(ar*math.cos(torad(nrot+rot1)))
newy=newy+sc*round(ar*math.sin(torad(nrot+rot1)))
end
table.insert(nvt,{["class"]=cpt.class,
["x"]=newx,
["y"]=newy})
end
--Check for "crossovers"
--New data type to store points with same coordinates
local mvt={}
local wnvt=wrap(nvt)
for i,p in ipairs(wnvt) do
table.insert(mvt,{["class"]={p.class},["x"]=p.x,["y"]=p.y})
end
--Number of merges so far
merges=0
for i=2,#wnvt,1 do
mi=i-merges
dx=wvt[i].x-wvt[i-1].x
dy=wvt[i].y-wvt[i-1].y
ndx=wnvt[i].x-wnvt[i-1].x
ndy=wnvt[i].y-wnvt[i-1].y
if (dy*ndy<0 or dx*ndx<0) then
--Multiplicities
c1=#mvt[mi-1].class
c2=#mvt[mi].class
--Weighted average
mvt[mi-1].x=(c1*mvt[mi-1].x+c2*mvt[mi].x)/(c1+c2)
mvt[mi-1].y=(c1*mvt[mi-1].y+c2*mvt[mi].y)/(c1+c2)
--Merge classes
mvt[mi-1].class={unpack(mvt[mi-1].class),unpack(mvt[mi].class)}
--Delete point
table.remove(mvt,mi)
merges=merges+1
end
end
--Rebuild wrapped new vector table
wnvt={}
for i,p in ipairs(mvt) do
for k,pclass in ipairs(p.class) do
table.insert(wnvt,{["class"]=pclass,["x"]=p.x,["y"]=p.y})
end
end
return unwrap(wnvt)
end
function merge_identical(vt)
local mvt = util.copy(vt)
i=2
lx=mvt[1].x
ly=mvt[1].y
while i<#mvt do
if mvt[i].x==lx and mvt[i].y==ly then
table.remove(mvt,i)
else
lx=mvt[i].x
ly=mvt[i].y
i=i+1
end
end
return mvt
end
--Returns chirality of vector shape. +1 if counterclockwise, -1 if clockwise
function get_chirality(vt)
local wvt=wrap(vt)
wvt=merge_identical(wvt)
trot=0
for i=2,#wvt-1,1 do
rot1=math.atan2(wvt[i].y-wvt[i-1].y,wvt[i].x-wvt[i-1].x)
rot2=math.atan2(wvt[i+1].y-wvt[i].y,wvt[i+1].x-wvt[i].x)
drot=todegree(rot2-rot1)%360
if drot>180 then drot=360-drot elseif drot==180 then drot=0 else drot=-1*drot end
trot=trot+drot
end
return sign(trot)
end
--Duplicates first and last coordinates at the end and beginning of shape,
--to allow for wraparound calculations
function wrap(vt)
local wvt={}
table.insert(wvt,util.copy(vt[#vt]))
for i=1,#vt,1 do
table.insert(wvt,util.copy(vt[i]))
end
table.insert(wvt,util.copy(vt[1]))
--Add linked list capability. Because. Hacky fix gogogogo
for i=2,#wvt-1 do
wvt[i].prev=wvt[i-1]
wvt[i].next=wvt[i+1]
end
--And link the start and end
wvt[2].prev=wvt[#wvt-1]
wvt[#wvt-1].next=wvt[2]
return wvt
end
--Cuts off the first and last coordinates, to undo the effects of "wrap"
function unwrap(wvt)
local vt={}
for i=2,#wvt-1,1 do
table.insert(vt,util.copy(wvt[i]))
end
return vt
end
--Main execution function
function blur_clip(sub,sel)
--GUI config
config=
{
{
class="label",
label="Blur size:",
x=0,y=0,width=1,height=1
},
{
class="floatedit",
name="bsize",
min=0,step=0.5,value=1,
x=1,y=0,width=1,height=1
},
{
class="label",
label="Blur position:",
x=0,y=1,width=1,height=1
},
{
class="dropdown",
name="bpos",
items={"outside","middle","inside"},
value="outside",
x=1,y=1,width=1,height=1
},
{
class="label",
label="Precision:",
x=0,y=2,width=1,height=1
},
{
class="intedit",
name="bprec",
min=1,max=4,value=2,
x=1,y=2,width=1,height=1
}
}
--Show dialog
pressed,results=aegisub.dialog.display(config,{"Go","Cancel"})
if pressed=="Cancel" then aegisub.cancel() end
--Size of the blur
bsize=results["bsize"]
--Scale exponent for all the numbers
sexp=results["bprec"]
--How far to offset the blur by
boffset=0
if results["bpos"]=="inside" then boffset=bsize
elseif results["bpos"]=="middle" then boffset=bsize/2 end
--How far to offset the next line read
lines_added=0
libLyger:set_sub(sub, sel)
for si,li in ipairs(sel) do
--Progress report
aegisub.progress.task("Processing line "..si.."/"..#sel)
aegisub.progress.set(100*si/#sel)
--Read in the line
line = libLyger.lines[li]
--Comment it out
line.comment=true
sub[li+lines_added]=line
line.comment=false
--Find the clipping shape
ctype,tvector=line.text:match("\\(i?clip)%(([^%(%)]+)%)")
--Cancel if it doesn't exist
if tvector==nil then
aegisub.log("Make sure all lines have a clip statement.")
aegisub.cancel()
end
--Get position and add
px,py = libLyger:get_pos(line)
if line.text:match("\\pos")==nil and line.text:match("\\move")==nil then
line.text=string.format("{\\pos(%d,%d)}",px,py)..line.text
end
--Round
local function rnd(num)
num=tonumber(num) or 0
if num<0 then
num=num-0.5
return math.ceil(num)
end
num=num+0.5
return math.floor(num)
end
--If it's a rectangular clip, convert to vector clip
if tvector:match("([%d%-%.]+),([%d%-%.]+),([%d%-%.]+),([%d%-%.]+)")~=nil then
_x1,_y1,_x2,_y2=tvector:match("([%d%-%.]+),([%d%-%.]+),([%d%-%.]+),([%d%-%.]+)")
tvector=string.format("m %d %d l %d %d %d %d %d %d",
rnd(_x1),rnd(_y1),rnd(_x2),rnd(_y1),rnd(_x2),rnd(_y2),rnd(_x1),rnd(_y2))
end
--The original table and original scale exponent
otable,oexp=make_vector_table(tvector)
--Effective scale and scale exponent
eexp=sexp-oexp+1
escale=2^(eexp-1)
--aegisub.log("Escale: %.2f",escale)
--The innermost line
iline = util.copy(line)
itable={}
if ctype=="iclip" then
itable=grow(otable,bsize*2^(oexp-1)-boffset,escale)
else
itable=grow(otable,-1*boffset,escale)
end
iline.text=iline.text:gsub("\\i?clip%([^%(%)]+%)","\\"..ctype.."("..sexp..","..vtable_to_string(itable)..")")
--Add it to the subs
sub.insert(li+lines_added+1,iline)
lines_added=lines_added+1
--Set default alpha values
dalpha={}
dalpha[1]=alpha_from_style(line.styleref.color1)
dalpha[2]=alpha_from_style(line.styleref.color2)
dalpha[3]=alpha_from_style(line.styleref.color3)
dalpha[4]=alpha_from_style(line.styleref.color4)
--First tag block
ftag=line.text:match("^{[^{}]*}")
if ftag==nil then
ftag="{}"
line.text="{}"..line.text
end
--List of alphas not yet accounted for in the first tag
unacc={}
if ftag:match("\\alpha")==nil then
if ftag:match("\\1a")==nil then table.insert(unacc,1) end
if ftag:match("\\2a")==nil then table.insert(unacc,2) end
if ftag:match("\\3a")==nil then table.insert(unacc,3) end
if ftag:match("\\4a")==nil then table.insert(unacc,4) end
end
--Add tags if any are unaccounted for
if #unacc>0 then
--If all the unaccounted-for alphas are equal, only add an "alpha" tag
_tempa=dalpha[unacc[1]]
_equal=true
for _k,_a in ipairs(unacc) do
if dalpha[_a]~=_tempa then _equal=false end
end
if _equal then line.text=line.text:gsub("^{","{\\alpha"..dalpha[unacc[1]])
else
for _k,ui in ipairs(unacc) do
line.text=line.text:gsub("^{","{\\"..ui.."a"..dalpha[ui])
end
end
end
prevclip=itable
for j=1,math.ceil(bsize*escale*2^(oexp-1)),1 do
--Interpolation factor
factor=j/(bsize*escale+1)
--Flip if it's an iclip
if ctype=="iclip" then factor=1-factor end
--Copy the line
tline = util.copy(line)
--Sub in the interpolated alphas
tline.text=tline.text:gsub("\\alpha([^\\{}]+)",
function(a) return "\\alpha"..interpolate_alpha(factor,a,"&HFF&") end)
tline.text=tline.text:gsub("\\([1-4]a)([^\\{}]+)",
function(a,b) return "\\"..a..interpolate_alpha(factor,b,"&HFF&") end)
--Write the correct clip
thisclip=grow(otable,j/escale-boffset,escale)
clipstring=vtable_to_string(thisclip)..vtable_to_string(reverse_vector_table(prevclip))
prevclip=thisclip
tline.text=tline.text:gsub("\\i?clip%([^%(%)]+%)","\\clip("..sexp..","..clipstring..")")
--Insert the line
sub.insert(li+lines_added+1,tline)
lines_added=lines_added+1
end
end
aegisub.set_undo_point(script_name)
end
rec:registerMacro(blur_clip)

View file

@ -1,77 +0,0 @@
--[[
==README==
Put a rectangular clip in the first line.
Highlight that line and all the other lines you want to add the clip to.
Run, and the position-shifted clip will be added to those lines
]]--
script_name = "Clip shifter"
script_description = "Reads a rectangular clip from the first line and places it on the other highlighted ones."
script_version = "0.2.0"
script_author = "lyger"
script_namespace = "lyger.ClipShifter"
local DependencyControl = require("l0.DependencyControl")
local rec = DependencyControl{
feed = "https://raw.githubusercontent.com/TypesettingTools/lyger-Aegisub-Scripts/master/DependencyControl.json",
{
{"lyger.LibLyger", version = "2.0.0", url = "http://github.com/TypesettingTools/lyger-Aegisub-Scripts"},
"aegisub.util"
}
}
local LibLyger, util = rec:requireModules()
local libLyger = LibLyger()
function clip_shift(sub,sel)
libLyger:set_sub(sub, sel)
--Read in first line
local first_line = libLyger.lines[sel[1]]
--Read in the clip
--No need to double check, since the validate function ensures this
local _,_,sclip1,sclip2,sclip3,sclip4=
first_line.text:find("\\clip%(([%d%.%-]*),([%d%.%-]*),([%d%.%-]*),([%d%.%-]*)%)")
sclip1=tonumber(sclip1)
sclip2=tonumber(sclip2)
sclip3=tonumber(sclip3)
sclip4=tonumber(sclip4)
--Get position
sx, sy = libLyger:get_pos(first_line)
for i=2,#sel,1 do
--Read the line
this_line = libLyger.lines[sel[i]]
--Get its position
tx, ty = libLyger:get_pos(this_line)
--Deltas
d_x,d_y=tx-sx,ty-sy
--Remove any existing rectangular clip
this_line.text=this_line.text:gsub("\\clip%(([%d%.%-]*),([%d%.%-]*),([%d%.%-]*),([%d%.%-]*)%)","")
--Add clip
this_line.text=string.format("{\\clip(%d,%d,%d,%d)}",
sclip1+d_x,sclip2+d_y,sclip3+d_x,sclip4+d_y)..this_line.text
this_line.text=this_line.text:gsub("}{","")
sub[sel[i]]=this_line
end
aegisub.set_undo_point(script_name)
end
--Make sure the first line contains a rectangular clip
function validate_clip_shift(sub,sel)
return #sel>1 and
sub[sel[1]].text:find("\\clip%(([%d%.%-]*),([%d%.%-]*),([%d%.%-]*),([%d%.%-]*)%)")~=nil
end
rec:registerMacro(clip_shift, validate_clip_shift)

View file

@ -1,398 +0,0 @@
[[
==README==
Frame-by-Frame Transform Automation Script
Smoothly transforms various parameters across multi-line, frame-by-frame typesets.
Useful for adding smooth transitions to frame-by-frame typesets that cannot be tracked with mocha,
or as a substitute for the stepped \t transforms generated by the Aegisub-Motion.lua script, which
may cause more lag than hard-coded values.
First generate the frame-by-frame typeset that you will be adding the effect to. Find the lines where
you want the effect to begin and the effect to end, and visually typeset them until they look the way
you want them to.
These lines will act as "keyframes", and the automation will modify all the lines in between so that
the appearance of the first line smoothly transitions into the appearance of the last line. Simply
highlight the first line, the last line, and all the lines in between, and run the automation.
It will only affect the tags that are checked in the popup menu when you run the automation. If you wish
to save specific sets of parameters that you would like to run together, you can use the presets manager.
For example, you can go to the presets manager, check all the color tags, and save a preset named "Colors".
The next time you want to transform all the colors, just select "Colors" from the preset dropdown menu.
The "All" preset is included by default and cannot be deleted. If you want a specific preset to be loaded
when you start the script, name it "Default" when you define the preset.
This may be obvious, but this automation only works on one layer or one component of a frame-by-frame
typeset at a time. If you have a frame-by-frame typeset that has two lines per frame, which looks like:
A1
B1
A2
B2
A3
B3
etc.
Then this automation will not work. The lines must be organized as:
A1
A2
A3
etc.
B1
B2
B3
etc.
And you would have to run the automation twice, once on A and once on B. Furthermore, the text of each
line must be exactly the same once all tags are removed. You can have as many tag blocks as you want
in whatever positions you want for the "keyframe" lines (the first and the last). But once the tags are
taken out, the text of the lines must be identical, down to the last space. If you are using ctrl-D or
copy-pasting, this should be a given, but it's worth a warning.
The lines in between can have any tags you want in them. So long as the automation is not transforming
those particular tags, they will be left untouched. If you need the typeset to suddenly turn huge for one
frame, simply uncheck "fscx" and "fscy" when you run the automation, and the size of the line won't be
touched.
If you are transforming rotations, there is something to watch out for. If you want a line to start
with \frz10 and rotate to \frz350, then with default options, the line will rotate 340 degrees around the
circle until it gets to 350. You probably wanted it to rotate only 20 degrees, passing through 0. The
solution is to check the "Rotate in shortest direction" checkbox from the popup window. This will cause
the line to always pick the rotation direction that has a total rotation of less than 180 degrees.
New feature: ignore text. Requires you to only have one tag block in each line, at the beginning.
Comes with an extra automation "Remove tags" that utilizes functions that were written for the main
automation. You can comment out (two dashes) the line at the bottom that adds this automation if you don't
want it.
TODO:
Check that all lines text match
iclip support
]]
export script_name = "Frame-by-frame transform"
export script_description = "Smoothly transforms between the first and last selected lines."
export script_version = "2.0.1"
export script_namespace = "lyger.FbfTransform"
DependencyControl = require "l0.DependencyControl"
rec = DependencyControl{
feed: "https://raw.githubusercontent.com/TypesettingTools/lyger-Aegisub-Scripts/master/DependencyControl.json",
{
{"lyger.LibLyger", version: "2.0.0", url: "http://github.com/TypesettingTools/lyger-Aegisub-Scripts"},
{"l0.Functional", version: "0.3.0", url: "https://github.com/TypesettingTools/ASSFoundation",
feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"},
}
}
LibLyger, Functional = rec\requireModules!
import list, math, string, table, unicode, util, re from Functional
logger, libLyger = rec\getLogger!, LibLyger!
-- tag list, grouped by dialog layout
tags_grouped = {
{"c", "2c", "3c", "4c"},
{"alpha", "1a", "2a", "3a", "4a"},
{"fscx", "fscy", "fax", "fay"},
{"frx", "fry", "frz"},
{"bord", "shad", "fs", "fsp"},
{"xbord", "ybord", "xshad", "yshad"},
{"blur", "be"},
{"pos", "org", "clip"}
}
tags_flat = list.join unpack tags_grouped
-- default settings for every preset
preset_defaults = { skiptext: false, flip_rot: false, accel: 1.0,
tags: {tag, false for tag in *tags_flat }
}
-- the default preset must always be available and cannot be deleted
config = rec\getConfigHandler {
presets: {
Default: {}
"[Last Settings]": {description: "Repeats the last #{script_name} operation"}
}
startupPreset: "Default"
}
unless config\load!
-- write example preset on first time load
config.c.presets["All"] = tags: {tag, true for tag in *tags_flat}
config\write!
create_dialog = (preset) ->
config\load!
preset_names = [preset for preset, _ in pairs config.c.presets]
table.sort preset_names
dlg = {
-- Flip rotation
{ name: "flip_rot", class: "checkbox", x: 0, y: 9, width: 3, height: 1,
label: "Rotate in shortest direction", value: preset.c.flip_rot },
{ name: "skiptext", class: "checkbox", x: 3, y: 9, width: 2, height: 1,
label: "Ignore text", value: preset.c.skiptext },
-- Acceleration
{ class: "label", x: 0, y: 10, width: 2, height: 1,
label: "Acceleration: ", },
{ name: "accel", class:"floatedit", x: 2, y: 10, width: 3, height: 1,
value: preset.c.accel, hint: "1 means no acceleration, >1 starts slow and ends fast, <1 starts fast and ends slow" },
{ class: "label", x: 0, y: 11, width: 2, height: 1,
label: "Preset: " },
{ name: "preset_select", class: "dropdown", x: 2, y: 11, width: 2, height: 1,
items: preset_names, value: preset.section[#preset.section] },
{ name: "preset_modify", class: "dropdown", x: 4, y: 11, width: 2, height: 1,
items: {"Load", "Save", "Delete", "Rename"}, value: "Load" }
}
-- generate tag checkboxes
for y, group in ipairs tags_grouped
dlg[#dlg+1] = { name: tag, class: "checkbox", x: x-1, y: y, width: 1, height: 1,
label: "\\#{tag}", value: preset.c.tags[tag] } for x, tag in ipairs group
btn, res = aegisub.dialog.display dlg, {"OK", "Cancel", "Mod Preset", "Create Preset"}
return btn, res, preset
save_preset = (preset, res) ->
preset\import res, nil, true
if res.__class != DependencyControl.ConfigHandler
preset.c.tags[k] = res[k] for k in *tags_flat
preset\write!
create_preset = (settings, name) ->
msg = if not name
"Onii-chan, what name would you like your preset to listen to?"
elseif name == ""
"Onii-chan, did you forget to name the preset?"
elseif config.c.presets[name]
"Onii-chan, it's not good to name a preset the same thing as another one~"
if msg
btn, res = aegisub.dialog.display {
{ class: "label", x: 0, y: 0, width: 2, height: 1, label: msg }
{ class: "label", x: 0, y: 1, width: 1, height: 1, label: "Preset Name: " },
{ class: "edit", x: 1, y: 1, width: 1, height: 1, name: "name", text: name }
}
return btn and create_preset settings, res.name
preset = config\getSectionHandler {"presets", name}, preset_defaults
save_preset preset, settings
return name
prepare_line = (i, preset) ->
line = libLyger.lines[i]
-- Figure out the correct position and origin values
posx, posy = libLyger\get_pos line
orgx, orgy = libLyger\get_org line
-- Look for clips
clip = {line.text\match "\\clip%(([%d%.%-]*),([%d%.%-]*),([%d%.%-]*),([%d%.%-]*)%)"}
-- Make sure each line starts with tags
line.text = "{}#{line.text}" unless line.text\find "^{"
-- Turn all \1c tags into \c tags, just for convenience
line.text = line.text\gsub "\\1c", "\\c"
--Separate line into a table of tags and text
line_table = if preset.c.skiptext
while not line.text\match "^{[^}]+}[^{]"
line.text = line.text\gsub "}{", "", 1
tag, text = line.text\match "^({[^}]+})(.+)$"
{{:tag, :text}}
else [{:tag, :text} for tag, text in line.text\gmatch "({[^}]*})([^{]*)"]
return line, line_table, posx, posy, orgx, orgy, #clip > 0 and clip
--The main body of code that runs the frame transform
frame_transform = (sub, sel, res) ->
-- save last settings
preset = config\getSectionHandler {"presets", "[Last Settings]"}, preset_defaults
save_preset preset, res
libLyger\set_sub sub
-- Set the first and last lines in the selection
first_line, start_table, sposx, sposy, sorgx, sorgy, sclip = prepare_line sel[1], preset
last_line, end_table, eposx, eposy, eorgx, eorgy, eclip = prepare_line sel[#sel], preset
-- If either the first or last line do not contain a rectangular clip,
-- you will not be clipping today
preset.c.tags.clip = false unless sclip and eclip
-- These are the tags to transform
transform_tags = [tag for tag in *tags_flat when preset.c.tags[tag]]
-- Make sure both lines have the same splits
LibLyger.match_splits start_table, end_table
-- Tables that store tables for each tag block, consisting of the state of all relevant tags
-- that are in the transform_tags table
start_state_table = LibLyger.make_state_table start_table, transform_tags
end_state_table = LibLyger.make_state_table end_table, transform_tags
-- Insert default values when not included for the state of each tag block,
-- or inherit values from previous tag block
start_style = libLyger\style_lookup first_line
end_style = libLyger\style_lookup last_line
current_end_state, current_start_state = {}, {}
for k, sval in ipairs start_state_table
-- build current state tables
for skey, sparam in pairs sval
current_start_state[skey] = sparam
for ekey, eparam in pairs end_state_table[k]
current_end_state[ekey] = eparam
-- check if end is missing any tags that start has
for skey, sparam in pairs sval
end_state_table[k][skey] or= current_end_state[skey] or end_style[skey]
-- check if start is missing any tags that end has
for ekey, eparam in pairs end_state_table[ k]
start_state_table[k][ekey] or= current_start_state[ekey] or start_style[ekey]
-- Insert proper state into each intervening line
for i = 2, #sel-1
aegisub.progress.set 100 * (i-1) / (#sel-1)
this_line = libLyger.lines[sel[i]]
-- Turn all \1c tags into \c tags, just for convenience
this_line.text = this_line.text\gsub "\\1c","\\c"
-- Remove all the relevant tags so they can be replaced with their proper interpolated values
this_line.text = LibLyger.time_exclude this_line.text, transform_tags
this_line.text = LibLyger.line_exclude this_line.text, transform_tags
this_line.text = this_line.text\gsub "{}",""
-- Make sure this line starts with tags
this_line.text = "{}#{this_line.text}" unless this_line.text\find "^{"
-- The interpolation factor for this particular line
factor = (i-1)^preset.c.accel / (#sel-1)^preset.c.accel
-- Handle pos transform
if preset.c.tags.pos then
x = LibLyger.float2str util.interpolate factor, sposx, eposx
y = LibLyger.float2str util.interpolate factor, sposy, eposy
this_line.text = this_line.text\gsub "^{", "{\\pos(#{x},#{y})"
-- Handle org transform
if preset.c.tags.org then
x = LibLyger.float2str util.interpolate factor, sorgx, eorgx
y = LibLyger.float2str util.interpolate factor, sorgy, eorgy
this_line.text = this_line.text\gsub "^{", "{\\org(#{x},#{y})"
-- Handle clip transform
if preset.c.tags.clip then
clip = [util.interpolate factor, ord, eclip[i] for i, ord in ipairs sclip]
logger\dump{clip, sclip, eclip}
this_line.text = this_line.text\gsub "^{", "{\\clip(%d,%d,%d,%d)"\format unpack clip
-- Break the line into a table
local this_table
if preset.c.skiptext
while not this_line.text\match "^{[^}]+}[^{]"
this_line.text = this_line.text\gsub "}{", "", 1
tag, text = this_line.text\match "^({[^}]+})(.+)$"
this_table = {{:tag, :text}}
else
this_table = [{:tag, :text} for tag, text in this_line.text\gmatch "({[^}]*})([^{]*)"]
-- Make sure it has the same splits
j = 1
while j <= #start_table
stext = start_table[j].text
ttext, ttag = this_table[j].text, this_table[j].tag
-- ttext might contain miscellaneous tags that are not being checked for,
-- so remove them temporarily
ttext_temp = ttext\gsub "{[^{}]*}", ""
-- If this table item has longer text, break it in two based on
-- the text of the start table
if #ttext_temp > #stext
newtext = ttext_temp\match "#{LibLyger.esc stext}(.*)"
for i = #this_table, j+1,-1
this_table[i+1] = this_table[i]
this_table[j] = tag: ttag, text: ttext\gsub "#{LibLyger.esc newtext}$",""
this_table[j+1] = tag: "{}", text: newtext
-- If the start table has longer text, then perhaps ttext was split
-- at a tag that's not being transformed
if #ttext < #stext
-- It should be impossible for this to happen at the end, but check anyway
assert this_table[j+1], "You fucked up big time somewhere. Sorry."
this_table[j].text = table.concat {ttext, this_table[j+1].tag, this_table[j+1].text}
if this_table[j+2]
this_table[i] = this_table[i+1] for i = j+1, #this_table-1
this_table[#this_table] = nil
j -= 1
j += 1
--Interpolate all the relevant parameters and insert
this_line.text = LibLyger.interpolate this_table, start_state_table, end_state_table,
factor, preset
sub[sel[i]] = this_line
validate_fbf = (sub, sel) -> #sel >= 3
load_tags_remove = (sub, sel) ->
pressed, res = aegisub.dialog.display {
{ class: "label", label: "Enter the tags you would like to remove: ",
x: 0, y: 0, width: 1,height: 1 },
{ class: "textbox", name: "tag_list", text: "",
x: 0, y: 1,width: 1, height: 1 },
{ class: "checkbox", label: "Remove all EXCEPT", name: "do_except", value: false,
x: 0,y: 2, width: 1, height: 1 }
}, {"Remove","Cancel"}, {ok: "Remove", cancel: "Cancel"}
return if pressed == "Cancel"
tag_list = [tag for tag in res.tag_list\gmatch "\\?(%w+)[%s\\n,;]*"]
--Remove or remove except the tags in the table
for li in *sel
line = sub[li]
f = res.do_except and LibLyger.line_exclude_except or LibLyger.line_exclude
line.text = f(line.text, tag_list)\gsub "{}", ""
sub[li] = line
fbf_gui = (sub, sel, _, preset_name = config.c.startupPreset) ->
preset = config\getSectionHandler {"presets", preset_name}, preset_defaults
btn, res = create_dialog preset
switch btn
when "OK" do frame_transform sub, sel, res
when "Create Preset" do fbf_gui sub, sel, nil, create_preset res
when "Mod Preset"
if preset_name != res.preset_select
preset = config\getSectionHandler {"presets", res.preset_select}, preset_defaults
preset_name = res.preset_select
switch res.preset_modify
when "Delete"
preset\delete!
preset_name = nil
when "Save" do save_preset preset, res
when "Rename"
preset_name = create_preset preset.userConfig, preset_name
preset\delete!
fbf_gui sub, sel, nil, preset_name
-- register macros
rec\registerMacros {
{script_name, nil, fbf_gui, validate_fbf},
{"Remove tags", "Remove or remove all except the input tags.", load_tags_remove}
}
for name, preset in pairs config.c.presets
f = (sub, sel) -> frame_transform sub, sel, config\getSectionHandler {"presets", name}
rec\registerMacro "Presets/#{name}", preset.description, f, validate_fbf, nil, true

View file

@ -1,227 +0,0 @@
--[[
==README==
No GUI, pretty straightforward.
For example, to make a line bend in an arc and transition from blue to red, do this:
{\frz10\c&HFF0000&}This is a line of tex{\frz350\c&H0000FF&}t
Run the automation and it'll add a tag before each character to make the rotation and color
transition change smoothly across the line.
Rotations are locked to less than 180 degree rotations. If you want a bend of more than 180,
then split it up into multiple rotations of less than 180 each. This script is meant for
convenience above all, so it runs with a single button press and no time-consuming options menu.
]]--
script_name = "Gradient by character"
script_description = "Smoothly transforms tags across your line, by character."
script_version = "1.3.1"
script_author = "lyger"
script_namespace = "lyger.GradientByChar"
local DependencyControl = require("l0.DependencyControl")
local rec = DependencyControl{
feed = "https://raw.githubusercontent.com/TypesettingTools/lyger-Aegisub-Scripts/master/DependencyControl.json",
{
{"lyger.LibLyger", version = "2.0.0", url = "http://github.com/TypesettingTools/lyger-Aegisub-Scripts"},
"aegisub.util", "aegisub.re"
}
}
local LibLyger, util, re = rec:requireModules()
local libLyger = LibLyger()
function grad_char(sub,sel)
libLyger:set_sub(sub, sel)
for si,li in ipairs(sel) do
--Read in the line
this_line=libLyger.lines[li]
--Make sure line starts with tags
if this_line.text:find("^{")==nil then this_line.text="{}"..this_line.text end
--Turn all \1c tags into \c tags, just for convenience
this_line.text=this_line.text:gsub("\\1c","\\c")
--Make line table
this_table={}
x=1
for thistag,thistext in this_line.text:gsub("}","}\t"):gmatch("({[^{}]*})([^{}]*)") do
this_table[x]={tag=thistag,text=thistext:gsub("\t","")}
x=x+1
end
if #this_table<2 then
aegisub.log("There must be more than one tag block in the line!")
return
end
--Transform these tags
transform_tags={
"c","2c","3c","4c",
"alpha","1a","2a","3a",
"fscx","fscy","fax","fay",
"frx","fry","frz",
"fs","fsp",
"bord","shad",
"xbord","ybord","xshad","yshad",
"blur","be"
}
--Make state table
this_state = LibLyger.make_state_table(this_table,transform_tags)
--Style lookup
this_style = libLyger:style_lookup(this_line)
--Running record of the state of the line
current_state={}
--Outer control loop
for i=2,#this_table,1 do
--Update current state
for ctag,cval in pairs(this_state[i-1]) do
current_state[ctag]=cval
end
--Stores state of each character, to prevent redundant tags
char_state=util.deep_copy(current_state)
--Local function for interpolation
local function handle_interpolation(factor,tag,sval,eval)
local param_type, ivalue = libLyger.param_type, ""
--Handle differently depending on the type of tag
if param_type[tag]=="alpha" then
ivalue=interpolate_alpha(factor,sval,eval)
elseif param_type[tag]=="color" then
ivalue=interpolate_color(factor,sval,eval)
elseif param_type[tag]=="angle" then
nstart=tonumber(sval)
nend=tonumber(eval)
--Use "Rotate in shortest direction" by default
nstart=nstart%360
nend=nend%360
ndelta=nend-nstart
if math.abs(ndelta)>180 then nstart=nstart+(ndelta*360)/math.abs(ndelta) end
--Interpolate
nvalue=interpolate(factor,nstart,nend)
if nvalue<0 then nvalue=nvalue+360 end
--Convert to string
ivalue=libLyger.float2str(nvalue)
elseif param_type[tag]=="number" then
nstart=tonumber(sval)
nend=tonumber(eval)
--Interpolate and convert to string
ivalue=libLyger.float2str(interpolate(factor,nstart,nend))
end
return ivalue
end
local ttext=this_table[i-1].text
if ttext:len()>0 then
--Rebuilt text
local rtext=""
--Skip the first character
local first=true
--Starting values
idx=1
matches=re.find(ttext,'\\\\[Nh]|\\X')
total=#matches
for _,match in ipairs(matches) do
ch=match.str
if not first then
--Interpolation factor
factor=idx/total
idx=idx+1
--Do nothing if the character is a space
if ch:find("%s")~=nil then
rtext=rtext..ch
else
--The tags in and out of the time statement
local non_time_tags=""
--Go through all the state tags in this tag block
for ttag,tparam in pairs(this_state[i]) do
--Figure out the starting state of the param
local sparam=current_state[ttag]
if sparam==nil then sparam=this_style[ttag] end
if type(sparam)~="number" then sparam=sparam:gsub("%)","") end--Just in case a \t tag snuck in
--Prevent redundancy
if sparam~=tparam then
--The string version of the interpolated parameter
local iparam=handle_interpolation(factor,ttag,sparam,tparam)
if iparam~=tostring(char_state[ttag]) then
non_time_tags=non_time_tags.."\\"..ttag..iparam
char_state[ttag]=iparam
end
end
end
if non_time_tags:len() < 1 then
--If no tags were added, do nothing
rtext=rtext..ch
else
--The final tag, with a star to indicate it was added through interpolation
rtext=rtext.."{*"..non_time_tags.."}"..ch
end
end
else
rtext=rtext..ch
end
first=false
end
this_table[i-1].text=rtext
end
end
rebuilt_text=""
for i,val in pairs(this_table) do
rebuilt_text=rebuilt_text..val.tag..val.text
end
this_line.text=rebuilt_text
sub[li]=this_line
end
aegisub.set_undo_point(script_name)
end
function remove_grad_char(sub,sel)
for si,li in ipairs(sel) do
this_line=sub[li]
this_line.text=this_line.text:gsub("{%*[^{}]*}","")
sub[li]=this_line
end
end
-- Register the macro
rec:registerMacros{
{"Apply Gradient", script_description, grad_char},
{"Remove Gradient", "Removes gradient generated by #{script_name}", remove_grad_char}
}

File diff suppressed because it is too large Load diff

View file

@ -1,175 +0,0 @@
--[[
README
Karaoke Helper
Does simple karaoke tasks. Adds blank padding syllables to the beginning of lines,
and also adjusts final syllable so it matches the line length.
Will add more features as ktimers suggest them to me.
]]--
script_name = "Karaoke helper"
script_description = "Miscellaneous tools for assisting in karaoke timing."
script_version = "0.2.0"
script_author = "lyger"
script_namespace = "lyger.KaraHelper"
local DependencyControl = require("l0.DependencyControl")
local rec = DependencyControl{
feed = "https://raw.githubusercontent.com/TypesettingTools/lyger-Aegisub-Scripts/master/DependencyControl.json",
{
{"lyger.LibLyger", version = "2.0.0", url = "http://github.com/TypesettingTools/lyger-Aegisub-Scripts"},
}
}
local LibLyger = rec:requireModules()
local libLyger = LibLyger()
function make_config(styles)
local stopts={"selected lines"}
for i=1,styles.n,1 do
stopts[i+1] = ("style: %q").format(styles[i].name)
end
local config=
{
--What to apply the automation on
{
class="label",
label="Apply to:",
x=0,y=0,width=1,height=1
},
{
class="dropdown",
name="sselect",items=stopts,
x=1,y=0,width=1,height=1,
value="selected lines"
},
--Match syls to line length
{
class="checkbox",
name="match",label="Match syllable lengths to line length",
x=0,y=1,width=2,height=1,
value=true
},
--Add blank syl at the start
{
class="checkbox",
name="leadin",label="Add start padding:",
x=0,y=2,width=1,height=1,
value=false
},
{
class="intedit",
name="leadindur",
x=1,y=2,width=1,height=1,
min=0,
value=0
},
--Add blank syl at the end
{
class="checkbox",
name="leadout",label="Add end padding:",
x=0,y=3,width=1,height=1,
value=false
},
{
class="intedit",
name="leadoutdur",
x=1,y=3,width=1,height=1,
min=0,
value=0
}
}
return config
end
--Match syllable and line durations
function match_durs(line)
local ldur=line.end_time-line.start_time
local cum_sdur=0
for sdur in line.text:gmatch("\\[Kk][fo]?(%d+)") do
cum_sdur=cum_sdur+tonumber(sdur)
end
local delta=math.floor(ldur/10)-cum_sdur
line.text=line.text:gsub("({[^{}]*\\[Kk][fo]?)(%d+)([^{}]*}[^{}]*)$",
function(pre,val,post)
return ("%s%d%s"):format(pre, tonumber(val)+delta, post)
end)
return line
end
--Add padding at the start
function add_prepad(line,pdur)
line.text=line.text:gsub("^({[^{}]*\\[Kk][fo]?)(%d+)",
function(pre,val)
return ("{\\k%d}%s%d"):format(pdur, pre, tonumber(val)-pdur)
end)
line.text=line.text:gsub("^{\\k(%d+)}({[^{}]*\\[Kk][fo]?)(%-?%d+)([^{}]*}{)",
function(val1,mid,val2,post)
return ("%s%d%s"):format(mid, tonumber(val1)+tonumber(val2), post)
end)
return line
end
--Add padding at the end
function add_postpad(line,pdur)
line.text=line.text:gsub("(\\[Kk][fo]?)(%d+)([^{}]*}[^{}]*)$",
function(pre,val,post)
return ("%s%d%s{\\k%d}"):format(pre, tonumber(val)-pdur, post, pdur)
end)
line.text=line.text:gsub("(\\[Kk][fo]?)(%-?%d+)([^{}]*}){\\k(%d+)}$",
function(pre,val1,mid,val2)
return ("%s%d%s"):format(pre, tonumber(val1)+tonumber(val2), mid)
end)
return line
end
--Load config and display
function load_kh(sub,sel)
libLyger:set_sub(sub, sel)
-- Basic header collection, config, dialog display
local config = make_config(libLyger.styles)
local pressed,results=aegisub.dialog.display(config)
if pressed=="Cancel" then aegisub.cancel() end
--Determine how to retrieve the next line, based on the dropdown selection
local tstyle, line_cnt, get_next = results["sselect"], #sub
if tstyle:match("^style: ") then
tstyle=tstyle:match("^style: \"(.+)\"$")
get_next = function(uindex)
for i = uindex, line_cnt do
local line = libLyger.dialogue[uindex]
if line.style == tstyle and (not line.comment or line.effect == "karaoke") then
return line, i
end
end
end
else
get_next = function(uindex)
if uindex <= #sel then
return libLyger.lines[sel[uindex]], uindex+1
end
end
end
--Control loop
local line, uindex = get_next(1)
while line do
if results["match"] then match_durs(line) end
if results["leadin"] then add_prepad(line, results["leadindur"]) end
if results["leadout"] then add_postpad(line, results["leadoutdur"]) end
sub[line.i] = line
line, uindex = get_next(uindex)
end
aegisub.set_undo_point(script_name)
end
rec:registerMacro(load_kh)

View file

@ -1,224 +0,0 @@
script_name = "Karaoke replacer"
script_description = "Replaces the syllables of a verse."
script_version = "0.3.0"
script_author = "lyger"
script_namespace = "lyger.KaraReplacer"
local DependencyControl = require("l0.DependencyControl")
local rec = DependencyControl{
feed = "https://raw.githubusercontent.com/TypesettingTools/lyger-Aegisub-Scripts/master/DependencyControl.json"
}
--Fuck it, I should comment this code. Her goes
function kara_replace(sub,sel)
for si,li in ipairs(sel) do
--Read in line
local line = sub[li]
--Split at karaoke tags and create table of tags and text
local line_table = {}
for tag,text in line.text:gmatch("({[^{}]*\\[kK][^{}]*})([^{}]*)") do
table.insert(line_table,{["tag"]=tag,["text"]=text})
end
--Put the line back together with spaces between syllables
local rebuilt_parts = {}
for i,val in ipairs(line_table) do
rebuilt_parts[i] = val.text
end
--Add some padding so it displays better
local rebuilt_original = table.concat(rebuilt_parts, " ")
rebuilt_original = rebuilt_original .. string.rep(" ", math.floor(rebuilt_original:len()/2) -1)
--Dialog display
local config = {
{
class="label",
label=rebuilt_original,
x=0,y=0,width=1,height=1
}
,
{
class="edit",
name="replace",
x=0,y=1,width=1,height=1
}
}
--Instructions
local help_config = {
{
class="label",
label=
"The syllables of the original line will be displayed.\n"..
"Type the syllables you would like to replace them with,\n"..
"with spaces between each syllable.\n\n"..
"If you want a space in the lyrics, type a double space.\n"..
"Two join a syllable with the one after it, put a + after\n"..
"the syllable.\n"..
"To split a syllable (you'll have to adjust it yourself),\n"..
"put a | where you want the split.\n"..
"To insert a blank syllable (for padding), type _\n"..
"You can ignore any blank syllables in the original line.\n\n"..
"Example:\n"..
"_ ko re wa+ re|i de su",
x=0,y=0,width=1,height=1
}
}
--Show dialog and get input for each line
local pressed
repeat
pressed,result=aegisub.dialog.display(config,{"Next line","Help"})
if pressed=="Help" then
aegisub.dialog.display(help_config,{"OK"})
end
until pressed~="Help"
--Split input at spaces and store in table
local replace = {}
for newsyl in result["replace"]:gsub(" ","\t "):gmatch("[^ ]+") do
newsyl=newsyl:gsub("\t"," ")
table.insert(replace,newsyl)
end
local rebuilt_text, r = {}, 1
--Indices of original and replacement tables
local oi, ri = 1, 1
while oi<=#line_table do
--Skip if it's a blank syl (used for padding) or we're out of replacements
if line_table[oi].text:len()>0 and replace[ri]~=nil then
--Handle splitting syls
if replace[ri]:find("|")~=nil then
--Split the replacement line at | characters
subtab={}
for subsyl in replace[ri]:gmatch("[^|]+") do
table.insert(subtab,subsyl)
end
--Find the original time of the karaoke syllable
local otime = tonumber(line_table[oi].tag:match("\\[kK][fo]?(%d+)"))
--The remaining time (for last syl, to ensure they add up to the original time)
local ltime = otime
--Add all but the last syl
for x=1,#subtab-1,1 do
--To minimize truncation error, alternate between ceil and floor
local ttime = 0
if x%2==1 then
ttime = math.floor(otime/#subtab)
else
ttime = math.ceil(otime/#subtab)
end
rebuilt_text[r] = line_table[oi].tag:gsub("(\\[kK][fo]?)%d+","\1"..tostring(ttime))
rebuilt_text[r+1], r = subtab[x], r+2
ltime=ltime-ttime
end
--Add the last syl
rebuilt_text[r] = line_table[oi].tag:gsub("(\\[kK][fo]?)%d+","\1"..tostring(ltime))
rebuilt_text[r+1], r = subtab[#subtab], r+2
--Handle merging syls
--Only merge if it's not the last syl
elseif replace[ri]:find("+")~=nil and oi<#line_table then
local temp_tag = line_table[oi].tag
oi=oi+1
stime=tonumber(line_table[oi].tag:match("\\[kK][fo]?(%d+)"))
temp_tag=temp_tag:gsub("(\\[kK][fo]?)(%d+)",function(a,b)
return a..tostring(tonumber(b)+stime)
end)
rebuilt_text[r], rebuilt_text[r+1] = temp_tag, replace[ri]:gsub("+","")
r = r+2
--The usual replacement
else
rebuilt_text[r], rebuilt_text[r+1] = line_table[oi].tag, replace[ri]:gsub("_","")
r = r+2
end
--Increment indices
oi=oi+1
ri=ri+1
else
rebuilt_text[r], r = line_table[oi].tag, r+1
oi=oi+1
end
end
line.text = table.concat(rebuilt_text)
sub[li]=line
if finished then break end
end
end
--Old behavior. If automations are ever modified so that hitting "enter" from a text box
--will execute the "OK" button, then this behavior is probably better.
--For now, this function doesn't do anything
function kara_replace_old(sub,sel)
for si,li in ipairs(sel) do
line=sub[li]
line_table={}
for tag,text in line.text:gmatch("({[^{}]*\\[kK][^{}]*})([^{}]*)") do
table.insert(line_table,{["tag"]=tag,["text"]=text})
end
rebuilt_text=""
finished=false
for i,val in ipairs(line_table) do
local function hl_syl(lt,idx)
result=""
for k,a in ipairs(lt) do
if k==idx then result=result.." ["..a.text:upper().."] "
else result=result..a.text end
end
return result
end
if val.text:len()<1 or finished then
rebuilt_text=rebuilt_text..val.tag..val.text
else
config=
{
{
class="label",
label="Enter the syllable to replace with, or nothing to close.",
x=0,y=0,width=1,height=1
},
{
class="label",
label=hl_syl(line_table,i),
x=0,y=2,width=1,height=1
},
{
class="edit",
name="replace",
x=0,y=3,width=1,height=1
}
}
_,res=aegisub.dialog.display(config,{"OK"})
if res["replace"]:len()<1 then
rebuilt_text=rebuilt_text..val.tag..val.text
finished=true
else
rebuilt_text=rebuilt_text..val.tag..res["replace"]
end
end
end
line.text=rebuilt_text
sub[li]=line
if finished then break end
end
end
rec:registerMacro(kara_replace)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,185 +0,0 @@
-- Times a sign with {TS 3:24} to 3:24-3:25. Can convert and use a few other formats, like {3:24}, {TS,3:24}, {3,24}, etc.
-- supported timecodes: {TS 1:23}, {TS 1:23 words}, {TS words 1:23}, {TS,1:23}, {1:23}, {1;23}, {1,23}, {1.23}, [1:23], and variations
-- Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#timesigns
script_name="Time Signs"
script_description="Rough-times signs from TS timecodes"
script_author="unanimated"
script_version="2.8"
script_namespace="ua.TimeSigns"
local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl")
if haveDepCtrl then
script_version="2.8.0"
depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub-Scripts/master/DependencyControl.json"}
end
function signtime(subs,sel)
for z=#sel,1,-1 do
i=sel[z]
line=subs[i]
text=line.text
-- format timecodes
text=text
:gsub("({.-})({TS.-})","%2%1")
:gsub("(%d+):(%d%d):(%d%d)",
function(a,b,c) return a*60+b..":"..c end) -- hours
:gsub("^(%d%d%d%d)%s%s*","{TS %1}") -- ^1234 text
:gsub("(%d%d)ish","%1") -- 1:23ish
:gsub("^([%d%s:,%-]+)","{%1}") -- ^1:23, 2:34, 4:56
:gsub("^(%d+:%d%d)%s%-%s","{TS %1}") -- ^12?:34 -
:gsub("^(%d+:%d%d%s*)","{TS %1}") -- ^12?:34
:gsub("^{(%d+:%d%d)","{TS %1") -- ^{12?:34
:gsub("^%[(%d+:%d%d)%]:?%s*","{TS %1}") -- ^[12?:34]:?
:gsub("{TS[%s%p]+(%d)","{TS %1") -- {TS ~12:34
:gsub("({[^}]-)(%d+)[%;%.,]?(%d%d)([^:%d][^}]-})","%1%2:%3%4") -- {1;23 / 1.23 / 1,23 123}
:gsub("{TS%s([^%d\\}]+)%s(%d+:%d%d)","{TS %2 %1") -- {TS comment 12:34}
:gsub(":%s?}","}") -- {TS 12:34: }
:gsub("|","\\N")
tc=text:match("^{[^}]-}") or ""
tc=tc:gsub("(%d+)(%d%d)([^:])","%1:%2%3")
text=text:gsub("^{[^}]-}%s*",tc)
if res.blur then text=text:gsub("\\blur[%d%.]+",""):gsub("{}",""):gsub("^","{\\blur"..res.bl.."}") end
line.text=text
tstags=text:match("{TS[^}]-}") or ""
times={} -- collect times if there are more
for tag in tstags:gmatch("%d+:%d+") do table.insert(times,tag) end
for t=#times,1,-1 do
tim=times[t]
-- convert to start time
tstid1,tstid2=tim:match("(%d+):(%d%d)")
if tstid1 then tid=(tstid1*60000+tstid2*1000-500) end
-- shifting times
if tid then
if res.shift then tid=tid+res.secs*1000 end
-- set start and end time [500ms before and after the timecode]
line.start_time=tid line.end_time=(tid+1000)
end
-- snapping to keyframes
if res.snap then
start=line.start_time
endt=line.end_time
startf=ms2fr(start)
endf=ms2fr(endt)
diff=250
diffe=250
startkf=keyframes[1]
endkf=keyframes[#keyframes]
-- check for nearby keyframes
for k,kf in ipairs(keyframes) do
-- startframe snap up to 24 frames back [scroll down to change default] and 5 frames forward
if kf>=startf-res.kfs and kf<startf+5 then
tdiff=math.abs(startf-kf)
if tdiff<=diff then diff=tdiff startkf=kf end
start=fr2ms(startkf)
line.start_time=start
end
-- endframe snap up to 24 frames forward [scroll down to change default] and 10 frames back
if kf>=endf-10 and kf<=endf+res.kfe then
tdiff=math.abs(endf-kf)
if tdiff<diffe then diffe=tdiff endkf=kf end
endt=fr2ms(endkf)
line.end_time=endt
end
end
end
line.text=line.text:gsub("{TS[^}]-}","{TS "..tim.."}")
if res.nots then line.text=line.text:gsub("{TS[^}]-}",""):gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}"):gsub("^({[^}]-})%s*","%1") end
subs.insert(i+1,line)
if t>1 then table.insert(sel,sel[#sel]+1) end
end
if #times>0 then subs.delete(i) end
end
if res.copy then taim=0 tame=0
for z,i in ipairs(sel) do
l=subs[i]
if l.start_time==0 then l.start_time=taim l.end_time=tame end
taim=l.start_time
tame=l.end_time
subs[i]=l
end
end
return sel
end
-- Config Stuff --
function saveconfig()
savecfg="Time Signs Settings\n\n"
for key,val in ipairs(GUI) do
if val.class=="floatedit" or val.class=="intedit" or val.class=="checkbox" and val.name~="save" then
savecfg=savecfg..val.name..":"..tf(res[val.name]).."\n"
end
end
file=io.open(cfgpath,"w")
file:write(savecfg)
file:close()
ADD({{class="label",label="Config saved to:\n"..cfgpath}},{"OK"},{close='OK'})
end
function loadconfig()
cfgpath=aegisub.decode_path("?user").."\\timesigns.conf"
file=io.open(cfgpath)
if file~=nil then
konf=file:read("*all")
file:close()
for key,val in ipairs(GUI) do
if val.class=="floatedit" or val.class=="checkbox" or val.class=="intedit" then
if konf:match(val.name) then val.value=detf(konf:match(val.name..":(.-)\n")) end
end
end
end
end
function tf(val)
if val==true then ret="true"
elseif val==false then ret="false"
else ret=val end
return ret
end
function detf(txt)
if txt=="true" then ret=true
elseif txt=="false" then ret=false
else ret=txt end
return ret
end
function timesigns(subs,sel)
ADD=aegisub.dialog.display
GUI={
{x=0,y=0,width=4,class="label",label="Check this if all your timecodes are too late or early:"},
{x=0,y=1,class="checkbox",name="shift",label="Shift timecodes by "},
{x=1,y=1,width=2,class="floatedit",name="secs",value=-10,hint="Negative=backward / positive=forward",step=0.5},
{x=3,y=1,class="label",label=" sec."},
{x=0,y=2,width=4,class="checkbox",name="copy",label="For lines without timecodes, copy from the previous line"},
{x=0,y=3,width=4,class="checkbox",name="nots",label="Automatically remove {TS ...} comments"},
{x=0,y=4,width=2,class="checkbox",name="blur",label="Automatically add blur:"},
{x=2,y=4,width=2,class="floatedit",name="bl",value="0.6"},
{x=0,y=5,width=2,class="checkbox",name="snap",label="Snapping to keyframes:",value=true},
{x=0,y=6,width=2,class="label",label="Frames to search back:"},
{x=0,y=7,width=2,class="label",label="Frames to search forward:"},
{x=2,y=6,width=2,class="intedit",name="kfs",value="24",step=1,min=1,max=250},
{x=2,y=7,width=2,class="intedit",name="kfe",value="24",step=1,min=1,max=250},
{x=0,y=8,width=2,class="checkbox",name="save",label="Save current settings"},
{x=2,y=8,width=2,class="label",label=" [Time Signs v"..script_version.."]"},
}
loadconfig()
buttons={"No more suffering with SHAFT signs!","Exit"}
P,res=ADD(GUI,buttons,{ok='No more suffering with SHAFT signs!',cancel='Exit'})
if P=="Exit" then aegisub.cancel() end
ms2fr=aegisub.frame_from_ms
fr2ms=aegisub.ms_from_frame
keyframes=aegisub.keyframes()
if res.save then saveconfig() end
if P=="No more suffering with SHAFT signs!" then sel=signtime(subs,sel) end
aegisub.set_undo_point(script_name)
return sel
end
if haveDepCtrl then depRec:registerMacro(timesigns) else aegisub.register_macro(script_name,script_description,timesigns) end

View file

@ -1,142 +0,0 @@
local json, log
version = '1.1.4'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'ConfigHandler',
:version,
description: 'A class for mapping dialogs to persistent configuration.',
author: 'torque',
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.ConfigHandler'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'json' }
{ 'a-mo.Log', version: '1.0.0' }
}
}
json, log = version\requireModules!
else
json = require 'json'
log = require 'a-mo.Log'
class ConfigHandler
@version: version
-- The minimum required format for `optionTables` is
-- { section: { optionname: { value: optionvalue, config: (true|false) } } }
-- the config key exists because this is designed to be embedded in dialog
-- tables. Some dialog elements may not be intended to be saved to a
-- config file, or are labels that do not return a value.
-- Constructor
new: ( @optionTables, fileName, hasSections, @version = "0.0.1", filePath = "?user" ) =>
@fileName = aegisub.decode_path "#{filePath}/#{fileName}"
@fileHandle = nil
loadDefault @
-- Private methods (I probably shouldn't have bothered to do this!)
loadDefault = =>
@configuration = { }
for sectionName, configEntries in pairs @optionTables
@configuration[sectionName] = { }
for optionName, configEntry in pairs configEntries
if configEntry.name != optionName and configEntry.class != "label"
configEntry.name = optionName
if configEntry.config
@configuration[sectionName][optionName] = configEntry.value
parse = =>
rawConfigText = @fileHandle\read '*a'
-- Avoid clobbering the things loaded by loadDefault. I need to
-- decide how I want to handle version changes between a script's
-- built-in defaults and the serialized configuration on disk. This
-- is currently biased towards a script's built-in defaults.
parsedConfig = json.decode rawConfigText
if parsedConfig
for sectionName, configEntries in pairs parsedConfig
if configSection = @configuration[sectionName]
for optionName, optionValue in pairs configEntries
if configSection[optionName] != nil
configSection[optionName] = optionValue
doInterfaceUpdate = ( interfaceSection, sectionName ) =>
for tableKey, tableValue in pairs interfaceSection
if tableValue.config and @configuration[sectionName][tableKey] != nil
tableValue.value = @configuration[sectionName][tableKey]
doConfigUpdate = ( newValues, sectionName ) =>
-- have to loop across @configuration because not all of the
-- fields in the result table are going to be serialized, and it
-- contains no information about which ones should be and which
-- ones should not be.
for configKey, configValue in pairs @configuration[sectionName]
if newValues[configKey] != nil
@configuration[sectionName][configKey] = newValues[configKey]
-- Public methods
read: =>
if @fileHandle = io.open @fileName, 'r'
parse @
@fileHandle\close!
return true
else
log.debug "Configuration file \"#{@fileName}\" can't be read. Writing defaults."
@write!
return false
-- todo: find keys missing from either @conf or interface, and warn
-- (maybe error?) about mismatching config versions.
updateInterface: ( sectionNames ) =>
if sectionNames
if "table" == type sectionNames
for sectionName in *sectionNames
if @configuration[sectionName]
doInterfaceUpdate @, @optionTables[sectionName], sectionName
else
log.debug "Cannot update section %s, as it doesn't exist.", sectionName
else
if @configuration[sectionNames]
doInterfaceUpdate @, @optionTables[sectionNames], sectionNames
else
log.debug "Cannot update section %s, as it doesn't exist.", sectionNames
else
for sectionName, section in pairs @optionTables
if @configuration[sectionName] != nil
doInterfaceUpdate @, section, sectionName
-- maybe updateConfigurationFromDialog (but then we're getting into
-- obj-c identifier verbosity territory, and I'd rather not go there)
updateConfiguration: ( resultTable, sectionNames ) =>
-- do nothing if sectionNames isn't defined.
if sectionNames
if "table" == type sectionNames
for section in *sectionNames
doConfigUpdate @, resultTable[section], section
else
doConfigUpdate @, resultTable, sectionNames
else
log.debug "Section Name not provided. You are doing it wrong."
write: =>
-- Make sure @configuration is not an empty table.
unless next( @configuration ) == nil
@configuration.__version = @version
serializedConfig = json.encode @configuration
@configuration.__version = nil
if @fileHandle = io.open @fileName, 'w'
@fileHandle\write serializedConfig
@fileHandle\close!
else
log.warn "Could not write the configuration file \"#{@fileName}\"."
delete: =>
os.remove @fileName
if haveDepCtrl
return version\register ConfigHandler
else
return ConfigHandler

View file

@ -1,132 +0,0 @@
local log
version = '1.0.5'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'DataHandler',
:version,
description: 'A class for parsing After Effects motion data.',
author: 'torque',
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.DataHandler'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'a-mo.Log', version: '1.0.0' }
}
}
log = version\requireModules!
else
log = require 'a-mo.Log'
class DataHandler
@version: version
new: ( input, @scriptResX, @scriptResY ) =>
-- (length-22)/4
if input
unless @parseRawDataString input
@parseFile input
parseRawDataString: ( rawDataString ) =>
@tableize rawDataString
if next @rawData
unless @rawData[1]\match "Adobe After Effects 6.0 Keyframe Data"
return false
width = @rawData[3]\match "Source Width[\t ]+([0-9]+)"
height = @rawData[4]\match "Source Height[\t ]+([0-9]+)"
unless width and height
return false
@xPosScale = @scriptResX/tonumber width
@yPosScale = @scriptResY/tonumber height
parse @
if #@xPosition != @length or #@yPosition != @length or #@xScale != @length or #@yScale != @length or #@zRotation != @length or 0 == @length
return false
return true
parseFile: ( fileName ) =>
if file = io.open fileName, 'r'
return @parseRawDataString file\read '*a'
return false
tableize: ( rawDataString ) =>
@rawData = { }
rawDataString\gsub "([^\r\n]+)", ( line ) ->
table.insert @rawData, line
parse = =>
-- Initialize these here so they don't get appended if
-- parseRawDataString is called twice.
@xPosition = { }
@yPosition = { }
@xScale = { }
@yScale = @xScale
@zRotation = { }
length = 0
section = 0
for _index, line in ipairs @rawData
unless line\match("^[\t ]+")
if line == "Position"
section = 1
elseif line == "Scale"
section = 2
elseif line == "Rotation"
section = 3
else
section = 0
else
line\gsub "^[\t ]+([%d%.%-]+)[\t ]+([%d%.%-e%+]+)(.*)", ( value1, value2, remainder ) ->
switch section
when 1
table.insert @xPosition, @xPosScale*tonumber value2
table.insert @yPosition, @yPosScale*tonumber remainder\match "^[\t ]+([%d%.%-e%+]+)"
length += 1
when 2
-- Sort of future proof against having different scale
-- values for different axes.
table.insert @xScale, tonumber value2
-- table.insert @yScale, tonumber value2
when 3
-- Sort of future proof having rotation around different
-- axes.
table.insert @zRotation, -tonumber value2
@length = length
-- Arguments: just your friendly neighborhood options table.
stripFields: ( options ) =>
defaults = { xPosition: @xStartPosition, yPosition: @yStartPosition, xScale: @xStartScale, zRotation: @zStartRotation }
for field, defaultValue in pairs defaults
unless options[field]
for index, value in ipairs @[field]
@[field][index] = defaultValue
checkLength: ( totalFrames ) =>
if totalFrames == @length
true
else
false
addReferenceFrame: ( frame ) =>
@startFrame = frame
@xStartPosition = @xPosition[frame]
@yStartPosition = @yPosition[frame]
@zStartRotation = @zRotation[frame]
@xStartScale = @xScale[frame]
@yStartScale = @yScale[frame]
calculateCurrentState: ( frame ) =>
@xCurrentPosition = @xPosition[frame]
@yCurrentPosition = @yPosition[frame]
@xRatio = @xScale[frame]/@xStartScale
@yRatio = @yScale[frame]/@yStartScale
@zRotationDiff = @zRotation[frame] - @zStartRotation
if haveDepCtrl
return version\register DataHandler
else
return DataHandler

View file

@ -1,72 +0,0 @@
local log, DataHandler, ShakeShapeHandler
version = '1.0.2'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'DataWrapper',
:version,
description: 'A class for wrapping motion data.',
author: 'torque',
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.DataWrapper'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'a-mo.Log', version: '1.0.0' }
{ 'a-mo.DataHandler', version: '1.0.5' }
{ 'a-mo.ShakeShapeHandler', version: '1.0.2' }
}
}
log, DataHandler, ShakeShapeHandler = version\requireModules!
else
log = require 'a-mo.Log'
DataHandler = require 'a-mo.DataHandler'
ShakeShapeHandler = require 'a-mo.ShakeShapeHandler'
class DataWrapper
@version: version
new: =>
tryDataHandler = ( input ) =>
@dataObject = DataHandler input, @scriptResX, @scriptResY
if @dataObject.length
@type = "TSR"
return true
return false
tryShakeShape = ( input ) =>
@dataObject = ShakeShapeHandler input, @scriptResY
if @dataObject.length
@type = "SRS"
return true
return false
bestEffortParsingAttempt: ( input, scriptResX, scriptResY ) =>
if "string" != type( input )
return false
@scriptResX, @scriptResY = tonumber( scriptResX ), tonumber( scriptResY )
if input\match '^Adobe After Effects 6.0 Keyframe Data'
if tryDataHandler @, input
return true
elseif input\match '^shake_shape_data 4.0'
if tryShakeShape @, input
return true
else
if tryDataHandler @, input
return true
if tryShakeShape @, input
return true
return false
if haveDepCtrl
return version\register DataWrapper
else
return DataWrapper

View file

@ -1,483 +0,0 @@
local util, json, log, tags, Transform
version = '1.5.3'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'Line',
:version,
description: 'A class for containing and manipulating a line.',
author: 'torque',
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.Line'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'aegisub.util' }
{ 'json' }
{ 'a-mo.Log', version: '1.0.0' }
{ 'a-mo.Tags', version: '1.3.4' }
{ 'a-mo.Transform', version: '1.2.4' }
}
}
util, json, log, tags, Transform = version\requireModules!
else
util = require 'aegisub.util'
json = require 'json'
log = require 'a-mo.Log'
tags = require 'a-mo.Tags'
Transform = require 'a-mo.Transform'
frameFromMs = aegisub.frame_from_ms
msFromFrame = aegisub.ms_from_frame
class Line
@version: version
fieldsToDeepCopy: {
'extra'
}
fieldsToCopy: {
-- Line fields
'actor', 'class', 'comment', 'effect', 'end_time', 'layer', 'margin_l', 'margin_r', 'margin_t', 'section', 'start_time', 'style', 'text'
-- Our fields
'number', 'transforms', 'transformShift', 'transformsAreTokenized', 'properties', 'styleRef', 'wasLinear'
}
splitChar: "\\\6"
tPlaceholder: ( count ) -> "\\\3#{count}\\\3"
tTokenPattern: "(\\\3(%d+)\\\3)"
defaultXPosition: {
-- align 3, 6, 9
( subResX, leftMargin, rightMargin ) ->
return subResX - rightMargin
-- align 1, 4, 7
( subResX, leftMargin, rightMargin ) ->
return leftMargin
-- align 2, 5, 8
( subResX, leftMargin, rightMargin ) ->
return 0.5*subResX
}
defaultYPosition: {
-- align 1, 2, 3
( subResY, verticalMargin ) ->
return subResY - verticalMargin
-- align 4, 5, 6
( subResY, verticalMargin ) ->
return 0.5*subResY
-- align 7, 8, 9
( subResY, verticalMargin ) ->
return verticalMargin
}
new: ( line, @parentCollection, overrides ) =>
for field in *@fieldsToDeepCopy
if "table" == type line[field]
-- safe to assume that all fields to be deep copied are expected
-- to be tables, otherwise they wouldn't be being deep copied
if "table" == type( overrides ) and "table" == type overrides[field]
@[field] = util.deep_copy overrides[field]
else
@[field] = util.deep_copy line[field]
else
if overrides[field] != nil
@[field] = overrides[field]
else
@[field] = line[field]
if "table" == type overrides
for field in *@fieldsToCopy
if overrides[field] != nil
@[field] = overrides[field]
else
@[field] = line[field]
else
for field in *@fieldsToCopy
@[field] = line[field]
@duration = @end_time - @start_time
-- Gathers extra line metrics: the alignment and position.
-- Returns false if there is not already a position tag in the line.
extraMetrics: ( styleRef = @styleRef ) =>
alignPattern = tags.allTags.align.pattern
posPattern = tags.allTags.pos.pattern
moveTag = tags.allTags.move
@runCallbackOnOverrides ( tagBlock ) =>
tagBlock\gsub alignPattern, ( value ) ->
unless @align
@align = tonumber value
tagBlock\gsub posPattern, ( value ) ->
unless @xPosition or @move
x, y = value\match "([%.%d%-]+),([%.%d%-]+)"
@xPosition, @yPosition = tonumber( x ), tonumber( y )
tagBlock\gsub moveTag.pattern, ( value ) ->
unless @xPosition or @move
@move = moveTag\convert value
unless @align
@align = styleRef.align
unless @xPosition or @move
@xPosition, @yPosition = @getDefaultPosition!
return false
return true
formatTime = ( time ) ->
seconds = time/1000
minutes = seconds/60
hours = minutes/60
return ("%d:%02d:%05.2f")\format math.floor( hours ), math.floor( minutes%60 ), seconds%60
__tostring: =>
@createRaw!
return @raw
createRaw: =>
line = {
(@comment and ("Comment: %d")\format( @layer ) or ("Dialogue: %d")\format( @layer ))
formatTime @start_time
formatTime @end_time
@style
@actor
@margin_l
@margin_r
@margin_t
@effect
@text
}
@raw = table.concat line, ','
generateTagIndex: ( major, minor ) ->
return tonumber tostring( major ) .. "." .. tostring minor
splitTagIndex: ( index ) ->
major = math.floor index
minor = tostring( index )\match "%d+.(%d+)"
return major, tonumber minor
-- Tries to guarantee there will be no redundantly duplicate tags in
-- the line. Does no other processing. Unfortunately, actually doing
-- this perfectly is very complicated because, for example, \t() is
-- actually position dependent. e.g. with \t(\c&HFF0000&)\c&HFF0000&,
-- the \t will not actually do anything.
deduplicateTags: =>
-- Combine contiguous override blocks.
@text = @text\gsub "}{", @splitChar
-- note: most tags can appear multiple times in a line and only the
-- last instance in a given tag block is used. Some tags (\pos,
-- \move, \org, \an) can only appear once and only the first
-- instance in the entire line is used.
tagCollection = { }
@runCallbackOnOverrides ( tagBlock, major ) =>
for tag in *tags.oneTimeTags
tagBlock = tagBlock\gsub tag.pattern, ( value ) ->
unless tagCollection[tag.name]
tagCollection[tag.name] = @.generateTagIndex major, tagBlock\find tag.pattern
return nil
else
log.debug "#{tag.name} previously found at #{tagCollection[tag.name]}"
return ""
return tagBlock
-- Quirks: 2 clips are allowed, as long as one is vector and one is
-- rectangular. Move and pos obviously conflict, and whichever is
-- the first is the one that's used. The same happens with fad and
-- fade. And again, the same with clip and iclip. Also, rectangular
-- clips can exist inside of transforms. If a rect clip exists in a
-- transform, its type (i or not) dictates the type of all rect
-- clips in the line.
for _, v in ipairs {
{ "move", "pos" }
{ "fade", "fad" }
{ "rectClip", "rectiClip" }
{ "vectClip", "vectiClip" }
}
if tagCollection[v[1]] and tagCollection[v[2]]
if tagCollection[v[1]] < tagCollection[v[2]]
-- get rid of tagCollection[v[2]]
@runCallbackOnOverrides ( tagBlock ) =>
tagBlock = tagBlock\gsub tags.allTags[v[2]].pattern, ""
else
-- get rid of tagCollection[v[1]]
@runCallbackOnOverrides ( tagBlock ) =>
tagBlock = tagBlock\gsub tags.allTags[v[1]].pattern, ""
@runCallbackOnOverrides ( tagBlock ) =>
for tag in *tags.repeatTags
-- Calculates the number of times the pattern will be replaced.
_, num = tagBlock\gsub tag.pattern, ""
-- Replaces all instances except the last one.
tagBlock = tagBlock\gsub tag.pattern, "", num - 1
return tagBlock
-- Now the whole thing has to be rerun on the contents of all
-- transforms.
@text = @text\gsub @splitChar, "}{"
@text = @text\gsub "{}", ""
@text = @text\gsub "\\clip%(%)", "" -- useless even inside transforms
-- Find the first instance of an override tag in a line following
-- startIndex.
-- Arguments:
-- tag [table]: A well-formatted tag table, probably taken from tags.allTags.
-- text [string]: The text that will be searched for the tag.
-- Default: @text, the entire line text.
-- startIndex [number]: A number specifying the point at which the
-- search should start.
-- Default: 1, the beginning of the provided text block.
-- Returns:
-- - The value of the tag.
-- On error:
-- - nil
-- - A string containing an error message.
getTagValue: ( tag, text = @text, startIndex = 1 ) =>
unless tag
return nil, "No tag table was supplied."
value = text\match tag.pattern, startIndex
if value
return tag\convert value
else
return nil, "The specified tag could not be found"
-- Find all instances of a tag in a line. Only looks through override
-- tag blocks.
getAllTagValues: ( tag ) =>
values = { }
@runCallbackOnOverrides ( tagBlock ) =>
value = @getTagValue tag, tagBlock
if value
table.insert values, value
return tagBlock
return values
-- Sets all values of a tag in a line. The provided table of values
-- must have the same number of tables
setAllTagValues: ( tag, values ) =>
replacements = 1
@runCallbackOnOverrides ( tagBlock ) =>
tagBlock, count = tagBlock\gsub tag.pattern, ->
tag.format\format values[replacements]
replacements += 1
return tagBlock
-- combines getAllTagValues and setAllTagValues by running the
-- provided callback on all of the values collected.
modifyAllTagValues: ( tag, callback ) =>
values = @getAllTagValues tag
-- Callback modifies the values table in whatever way.
callback @, values
@setAllTagValues tag, values
-- Adds an empty override tag to the beginning of the line text if
-- there is not an override tag there already.
ensureLeadingOverrideBlockExists: =>
if '{' != @text\sub 1, 1
@text = "{}" .. @text
-- Runs the provided callback on all of the override tag blocks
-- present in the line.
runCallbackOnOverrides: ( callback, count ) =>
major = 0
@text = @text\gsub "({.-})", ( tagBlock ) ->
major += 1
return callback @, tagBlock, major,
count
-- Runs the provided callback on the first override tag block in the
-- line, provided that override tag occurs before any other text in
-- the line.
runCallbackOnFirstOverride: ( callback ) =>
@text = @text\gsub "^({.-})", ( tagBlock ) ->
return callback @, tagBlock
-- Runs the provided callback on all overrides that aren't the first
-- one.
runCallbackOnOtherOverrides: ( callback ) =>
@text = @text\sub( 1, 1 ) .. @text\sub( 2, -1 )\gsub "({.-})", ( tagBlock ) ->
return callback @, tagBlock
getPropertiesFromStyle: ( styleRef = @styleRef ) =>
@properties = { }
for tag in *tags.styleTags
switch tag.type
when "alpha"
@properties[tag] = tag\convert styleRef[tag.style]\sub( 3, 4 )
when "color"
@properties[tag] = tag\convert styleRef[tag.style]\sub( 5, 10 )
else
@properties[tag] = tag\convert styleRef[tag.style]
-- Because duplicate tags may exist within transforms, it becomes
-- useful to remove transforms from a line before doing various
-- processing.
tokenizeTransforms: =>
unless @transformsAreTokenized
@transforms = { }
count = 0
tagIndex = 0
@runCallbackOnOverrides ( tagBlock ) =>
tagIndex += 1
return tagBlock\gsub tags.allTags.transform.pattern, ( transform ) ->
count += 1
token = @.tPlaceholder count
transform = Transform\fromString transform, @duration, tagIndex, @
transform.token = token
@transforms[count] = transform
-- create a token for the transforms
return token
@transformsAreTokenized = true
loopOverTokenizedTransforms: ( callback ) =>
if @transformsAreTokenized
@runCallbackOnOverrides ( tagBlock ) =>
return tagBlock\gsub @tTokenPattern, ( placeholder, index ) ->
return callback @transforms[tonumber index], placeholder
detokenizeTransformsCopy: ( shift = 0 ) =>
if @transformsAreTokenized
return @text\gsub "({.-})", ( tagBlock ) ->
return tagBlock\gsub @tTokenPattern, ( placeholder, index ) ->
transform = @transforms[tonumber index]
transform.startTime -= shift
transform.endTime -= shift
result = transform\toString!
transform.startTime += shift
transform.endTime += shift
return result
detokenizeTransforms: ( shift = 0 ) =>
@loopOverTokenizedTransforms ( transform, placeholder ) ->
transform.startTime -= shift
transform.endTime -= shift
result = transform\toString!
transform.startTime += shift
transform.endTime += shift
return result
@transformsAreTokenized = false
-- detokenize using transform.rawString
dontTouchTransforms: =>
@loopOverTokenizedTransforms ( transform, placeholder ) ->
return "\\t" .. transform.rawString
@transformsAreTokenized = false
interpolateTransformsCopy: ( shift = 0, start = @start_time ) =>
newText = @text
@loopOverTokenizedTransforms ( transform, placeholder ) ->
transform.startTime -= shift
transform.endTime -= shift
frame = frameFromMs start
newText = transform\interpolate @, newText, placeholder, math.floor( 0.5*( msFromFrame( frame ) + msFromFrame( frame + 1 ) ) ) - start
transform.startTime += shift
transform.endTime += shift
return nil
return newText
interpolateTransforms: ( shift = 0, start = @start_time ) =>
newText = @text
@loopOverTokenizedTransforms ( transform, placeholder ) ->
transform.startTime -= shift
transform.endTime -= shift
frame = frameFromMs start
newText = transform\interpolate @, newText, placeholder, math.floor( 0.5*( msFromFrame( frame ) + msFromFrame( frame + 1 ) ) ) - start
transform.startTime += shift
transform.endTime += shift
return nil
@text = newText
@transformsAreTokenized = false
shiftKaraoke: ( shift = @karaokeShift ) =>
karaokeTag = tags.allTags.karaoke
@runCallbackOnOverrides ( tagBlock ) =>
return tagBlock\gsub karaokeTag.pattern, ( ... ) ->
time = karaokeTag\convert ...
if shift > 0
oldShift = -shift
newTime = time - shift
shift -= time
if newTime > 0
if karaokeTag.tag == "\\kf"
return karaokeTag\format( oldShift ) .. karaokeTag\format time
else
return karaokeTag\format newTime
else
return ""
else
return nil
combineWithLine: ( line ) =>
if @text == line.text and @style == line.style and (@start_time == line.end_time or @end_time == line.start_time)
@start_time = math.min @start_time, line.start_time
@end_time = math.max @end_time, line.end_time
return true
return false
delete: ( sub = @parentCollection.sub ) =>
unless sub
log.windowError "Sub doesn't exist, so I can't delete things. This isn't gonna work."
unless @hasBeenDeleted
sub.delete @number
@hasBeenDeleted = true
getDefaultPosition: ( styleRef = @styleRef ) =>
verticalMargin = if @margin_t == 0 then styleRef.margin_t else @margin_t
leftMargin = if @margin_l == 0 then styleRef.margin_l else @margin_l
rightMargin = if @margin_r == 0 then styleRef.margin_r else @margin_r
align = @align or styleRef.align
return @defaultXPosition[align%3+1]( @parentCollection.meta.PlayResX, leftMargin, rightMargin ), @defaultYPosition[math.ceil align/3]( @parentCollection.meta.PlayResY, verticalMargin )
setExtraData: ( field, data ) =>
if "table" != type @extra
@extra = {}
switch type data
when "table"
@extra[field] = json.encode data
when "string"
@extra[field] = data
else
@extra[field] = tostring data
getExtraData: ( field ) =>
if "table" != type @extra
return nil
value = @extra[field]
success, res = pcall json.decode, value
-- Should probably add something for luabins here but it is
-- extremely stupid and dumb so I really don't want to.
if success
return res
else
return value
if haveDepCtrl
return version\register Line
else
return Line

View file

@ -1,272 +0,0 @@
local log, Line
version = '1.3.0'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'LineCollection'
:version
description: 'A class for handling collections of lines.'
author: 'torque'
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.LineCollection'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'a-mo.Log', version: '1.0.0' }
{ 'a-mo.Line', version: '1.5.3' }
}
}
log, Line = version\requireModules!
else
log = require 'a-mo.Log'
Line = require 'a-mo.Line'
frameFromMs = aegisub.frame_from_ms
class LineCollection
@version: version
@fromAllLines: ( sub, validationCb, selectLines ) =>
sel = { }
for i = 1, #@sub
table.insert( sel, i ) if @sub[i].class == "dialogue"
@ sub, sel, validationCb, selectLines
new: ( @sub, sel, validationCb, selectLines = true ) =>
@lines = { }
meta = getmetatable @
if 'function' != type meta.__index
metaIndex = meta.__index
meta.__index = ( index ) =>
if 'number' == type index
@lines[index]
else
metaIndex[index]
if type( sel ) == "table" and #sel > 0
@collectLines sel, validationCb, selectLines
if frameFromMs 0
@getFrameInfo!
else
for i = #@sub, 1, -1
if @sub[i].class != "dialogue" then
@firstLineNumber = i + 1
@lastLineNumber = i + 1
break
-- This method should update various properties such as
-- (start|end)(Time|Frame).
addLine: ( line, validationCb = (-> return true), selectLine = true, index = false ) =>
if validationCb line
line.parentCollection = @
line.inserted = false
line.selected = selectLine
line.number = index == true and line.number or index or nil
-- if @startTime is unset, @endTime should damn well be too.
if @startTime
if @startTime > line.start_time
@startTime = line.start_time
if @endTime < line.end_time
@endTime = line.end_time
else
@startTime = line.start_time
@endTime = line.end_time
if @hasMetaStyles
line.styleRef = @styles[line.style]
if @hasFrameInfo
line.startFrame = frameFromMs line.start_time
line.endFrame = frameFromMs line.end_time
@startFrame = frameFromMs @startTime
@endFrame = frameFromMs @endTime
@totalFrames = @endFrame - @startFrame
table.insert @lines, line
generateMetaAndStyles: =>
@styles = { }
@meta = { }
for i = 1, #@sub
line = @sub[i]
if line.class == "style"
@styles[line.name] = line
-- not going to bother porting all the special-case bullshit over
-- from karaskel.
elseif line.class == "info"
@meta[line.key] = line.value
elseif line.class == "dialogue"
break
unless next @styles
log.windowError "No styles could be found and I guarantee that's gonna break something."
@hasMetaStyles = true
collectLines: ( sel, validationCb = (( line ) -> return not line.comment), selectLines = true ) =>
unless @hasMetaStyles
@generateMetaAndStyles!
dialogueStart = 0
for x = 1, #@sub
if @sub[x].class == "dialogue"
dialogueStart = x - 1 -- start line of dialogue subs
break
@startTime = @sub[sel[1]].start_time
@endTime = @sub[sel[1]].end_time
@lastLineNumber = 0
for i = #sel, 1, -1
with line = Line @sub[sel[i]], @
if validationCb line
.number = sel[i]
@firstLineNumber = math.min .number, @firstLineNumber or .number
@lastLineNumber = math.max .number, @lastLineNumber
.inserted = true
.hasBeenDeleted = false
.selected = selectLines
.humanizedNumber = .number - dialogueStart
.styleRef = @styles[.style]
if .start_time < @startTime
@startTime = .start_time
if .end_time > @endTime
@endTime = .end_time
table.insert @lines, line
getFrameInfo: =>
for line in *@lines
line.startFrame = frameFromMs line.start_time
line.endFrame = frameFromMs line.end_time
@startFrame = frameFromMs @startTime
@endFrame = frameFromMs @endTime
@totalFrames = @endFrame - @startFrame
@hasFrameInfo = true
callMethodOnAllLines: ( methodName, ... ) =>
for line in *@lines
line[methodName] line, ...
combineIdenticalLines: =>
lastLine = @lines[1]
linesToSkip = { }
for i = 2, #@lines
log.checkCancellation!
if lastLine\combineWithLine @lines[i]
linesToSkip[#linesToSkip+1] = @lines[i]
@shouldInsertLines = true
continue
else lastLine = @lines[i]
@deleteLines linesToSkip
-- The third value passed to the callback is for progress reporting only,
-- and the fourth is the actual index.
runCallback: ( callback, reverse ) =>
lineCount = #@lines
if reverse
for index = lineCount, 1, -1
callback @, @lines[index], lineCount - index + 1, index
else
for index = 1, lineCount
callback @, @lines[index], index, index
deleteLines: ( lines = @lines, doShift = true ) =>
if lines.__class == Line
lines = { lines }
lineSet = {line,true for _,line in pairs lines when not line.hasBeenDeleted}
-- make sure all lines are unique and have not actually been already removed
lines = [k for k,v in pairs lineSet]
@sub.delete [line.number for line in *lines when line.inserted]
@lastLineNumber = @firstLineNumber
shift = #lines or 0
for line in *@lines
if lineSet[line]
line.hasBeenDeleted = true
shift -= line.inserted and 1 or 0
elseif not line.hasBeenDeleted and line.inserted
line.number -= doShift and shift or 0
@lastLineNumber = math.max(line.number, @lastLineNumber)
insertLines: =>
toInsert = [line for line in *@lines when not (line.inserted or line.hasBeenDeleted)]
tailLines, numberedLines = {}, {}
for i = 1, #toInsert
line = toInsert[i]
if line.number
numberedLines[#numberedLines + 1] = line
line.i = i
else
tailLines[#tailLines + 1] = line
line.number = @lastLineNumber + i
line.inserted = true
table.sort numberedLines, ( a, b ) ->
return (a.number < b.number) or (a.number == b.number) and (a.i < b.i)
for line in *numberedLines
@sub.insert line.number, line
line.inserted = true
@lastLineNumber = math.max @lastLineNumber, line.number
tailLineCnt, chunkSize = #tailLines, 1000
if tailLineCnt > 0
for i = 1, tailLineCnt, chunkSize
chunkSize = math.min chunkSize, tailLineCnt - i + 1
@sub.insert @lastLineNumber + i, unpack tailLines, i, i+chunkSize-1
@lastLineNumber = math.max @lastLineNumber, tailLines[tailLineCnt].number
replaceLines: =>
if @shouldInsertLines
@insertLines!
else
for line in *@lines
if line.inserted and not line.hasBeenDeleted
@sub[line.number] = line
getSelection: =>
sel = [line.number for line in *@lines when line.selected and line.inserted and not line.hasBeenDeleted]
return sel, sel[#sel]
__newindex: ( index, value ) =>
if 'number' == type index
@lines[index] = value
else
rawset @, index, value
__len: =>
#@lines
__ipairs: =>
iterator = ( tbl, i ) ->
i += 1
value = tbl[i]
if value
i, value
iterator, @lines, 0
-- There's no real reason to use pairs, but I've preserved it anyways
__pairs: =>
next, @lines, nil
if haveDepCtrl
return version\register LineCollection
else
return LineCollection

View file

@ -1,63 +0,0 @@
return {
version: "1.0.0"
debug: (...) ->
aegisub.log 4, ...
aegisub.log 4, '\n'
warn: (...) ->
aegisub.log 2, ...
aegisub.log 2, '\n'
-- I am not sure this is the logical place for this function.
checkCancellation: ->
if aegisub.progress.is_cancelled!
aegisub.cancel!
dump: ( item, ignore ) ->
level = 2
if "table" != type item
aegisub.log level, tostring item
aegisub.log level, "\n"
return
count = 1
tablecount = 1
result = { "{ @#{tablecount}" }
seen = { [item]: tablecount }
recurse = ( item, space ) ->
for key, value in pairs item
unless key == ignore
if "number" == type key
key = "##{key}"
if "table" == type value
unless seen[value]
tablecount += 1
seen[value] = tablecount
count += 1
result[count] = space .. "#{key}: { @#{tablecount}"
recurse value, space .. " "
count += 1
result[count] = space .. "}"
else
count += 1
result[count] = space .. "#{key}: @#{seen[value]}"
else
if "string" == type value
value = ("%q")\format value
count += 1
result[count] = space .. "#{key}: #{value}"
recurse item, " "
count += 1
result[count] = "}\n"
aegisub.log level, table.concat result, "\n"
windowError: ( errorMessage ) ->
aegisub.dialog.display { { class: "label", label: errorMessage } }, { "&Close" }, { cancel: "&Close" }
aegisub.cancel!
}

View file

@ -1,20 +0,0 @@
return {
version: "1.0.0"
round: ( num, idp ) ->
mult = 10^(idp or 0)
math.floor( num * mult + 0.5 ) / mult
dCos: (a) ->
math.cos math.rad a
dSin: (a) ->
math.sin math.rad a
dAtan: (y, x) ->
math.deg math.atan2 y, x
uuid: ->
('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx')\gsub "[xy]", ( char ) ->
('%x')\format char=="x" and math.random( 0, 15 ) or math.random 8, 11
}

View file

@ -1,312 +0,0 @@
local log, Line, LineCollection, Math, tags, Transform
version = '1.1.8'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'MotionHandler'
:version
description: 'A class for applying motion data to a LineCollection.'
author: 'torque'
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.MotionHandler'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'a-mo.Log', version: '1.0.0' }
{ 'a-mo.Line', version: '1.5.3' }
{ 'a-mo.LineCollection', version: '1.3.0' }
{ 'a-mo.Math', version: '1.0.0' }
{ 'a-mo.Tags', version: '1.3.4' }
{ 'a-mo.Transform', version: '1.2.4' }
}
}
log, Line, LineCollection, Math, tags, Transform = version\requireModules!
else
Line = require 'a-mo.Line'
LineCollection = require 'a-mo.LineCollection'
log = require 'a-mo.Log'
Math = require 'a-mo.Math'
tags = require 'a-mo.Tags'
Transform = require 'a-mo.Transform'
class MotionHandler
@version: version
new: ( @lineCollection, mainData, rectClipData = { }, vectClipData = { } ) =>
-- Create a local reference to the options table.
@options = @lineCollection.options
@lineTrackingData = mainData.dataObject
@rectClipData = rectClipData.dataObject
@vectClipData = vectClipData.dataObject
@xDelta = 0
@yDelta = 0
@callbacks = { }
-- Do NOT perform any normal callbacks if mainData is shake
-- rotoshape. In theory it would be possible to do plain translation
-- because the SRS data contains a center_x and center_y field for
-- each frame.
unless 'SRS' == mainData.type or @options.main.clipOnly
if @options.main.xPosition or @options.main.yPosition or @options.main.xScale or @options.main.zRotation
if @options.main.absPos
@callbacks["(\\pos)%(([%-%d%.]+,[%-%d%.]+)%)"] = absolutePosition
else
@callbacks["(\\pos)%(([%-%d%.]+,[%-%d%.]+)%)"] = position
if @options.main.origin
@callbacks["(\\org)%(([%-%d%.]+,[%-%d%.]+)%)"] = origin
if @options.main.xScale then
@callbacks["(\\fsc[xy])([%d%.]+)"] = scale
if @options.main.border
@callbacks["(\\[xy]?bord)([%d%.]+)"] = scale
if @options.main.shadow
@callbacks["(\\[xy]?shad)([%-%d%.]+)"] = scale
if @options.main.blur
@callbacks["(\\blur)([%d%.]+)"] = blur
if @options.main.zRotation
@callbacks["(\\frz?)([%-%d%.]+)"] = rotate
-- Don't support SRS for rectangular clips.
if @rectClipData and 'SRS' != rectClipData.type
@callbacks['(\\i?clip)(%([%-%d%.]+,[%-%d%.]+,[%-%d%.]+,[%-%d%.]+%))'] = rectangularClip
if @vectClipData
if 'SRS' == vectClipData.type
@callbacks['(\\i?clip)%(([^,]-)%)'] = vectorClipSRS
else
@callbacks['(\\i?clip)(%([^,]-%))'] = vectorClip
@resultingCollection = LineCollection @lineCollection.sub
@resultingCollection.shouldInsertLines = true
@resultingCollection.options = @options
-- This has to be copied over for clip interpolation
@resultingCollection.meta = @lineCollection.meta
for line in *@lineCollection.lines
if @options.main.linear and not (@options.main.origin and line.hasOrg) and not ((@rectClipData or @vectClipData) and line.hasClip)
line.method = linear
else
line.method = nonlinear
applyMotion: =>
setProgress = aegisub.progress.set
setProgress 0
totalLines = #@lineCollection.lines
-- The lines are collected in reverse order in LineCollection so
-- that we don't need to do things in reverse here.
insertNumber = @lineCollection.lines[totalLines].number
for index = 1, totalLines
with line = @lineCollection.lines[index]
-- start frame of line relative to start frame of tracked data
.relativeStart = .startFrame - @lineCollection.startFrame + 1
-- end frame of line relative to start frame of tracked data
.relativeEnd = .endFrame - @lineCollection.startFrame
.number = insertNumber
.method @, line
setProgress index/totalLines*100
return @resultingCollection
linear = ( line ) =>
moveTag = tags.allTags.move
posTag = tags.allTags.pos
with line
startFrameTime = aegisub.ms_from_frame aegisub.frame_from_ms .start_time
frameAfterStartTime = aegisub.ms_from_frame aegisub.frame_from_ms( .start_time ) + 1
frameBeforeEndTime = aegisub.ms_from_frame aegisub.frame_from_ms( .end_time ) - 1
endFrameTime = aegisub.ms_from_frame aegisub.frame_from_ms .end_time
-- Calculates the time length (in ms) from the start of the first
-- subtitle frame to the actual start of the line time.
beginTime = math.floor 0.5*(startFrameTime + frameAfterStartTime) - .start_time
-- Calculates the total length of the line plus the difference
-- (which is negative) between the start of the last frame the
-- line is on and the end time of the line.
endTime = math.floor 0.5*(frameBeforeEndTime + endFrameTime) - .start_time
if .move
.text = .text\gsub moveTag.pattern, ->
move = .move
progress = (.start_time - move.start)/(move.end - move.start)
return posTag\format moveTag\interpolate {move.x1, move.y1}, {move.x2, move.y2}, progress
for pattern, callback in pairs @callbacks
log.checkCancellation!
.text = .text\gsub pattern, ( tag, value ) ->
values = { }
for frame in *{ .relativeStart, .relativeEnd }
@lineTrackingData\calculateCurrentState frame
values[#values+1] = callback @, value, frame
("%s%s\\t(%d,%d,%s%s)")\format tag, values[1], beginTime, endTime, tag, values[2]
if @options.main.xPosition or @options.main.yPosition
.text = .text\gsub "\\pos(%b())\\t%((%d+,%d+),\\pos(%b())%)", ( start, time, finish ) ->
"\\move" .. start\sub( 1, -2 ) .. ',' .. finish\sub( 2, -2 ) .. ',' .. time .. ")"
@resultingCollection\addLine Line( line, nil, { wasLinear: true } ), nil, true, true
nonlinear = ( line ) =>
moveTag = tags.allTags.move
posTag = tags.allTags.pos
for frame = line.relativeEnd, line.relativeStart, -1
with line
log.checkCancellation!
newStartTime = math.floor(math.max(0, aegisub.ms_from_frame( @lineCollection.startFrame + frame - 1 ))/10)*10
newEndTime = math.floor(aegisub.ms_from_frame( @lineCollection.startFrame + frame )/10)*10
timeDelta = newStartTime - math.floor(math.max(0,aegisub.ms_from_frame( @lineCollection.startFrame + .relativeStart - 1 ))/10)*10
local newText
if @options.main.killTrans
newText = \interpolateTransformsCopy timeDelta, newStartTime
else
newText = \detokenizeTransformsCopy timeDelta
fadeTag = tags.allTags.fade
if @options.main.killTrans
local fade
newText = newText\gsub "({.-})", ( tagBlock ) -> tagBlock\gsub fadeTag.pattern, ( value ) ->
fade = fadeTag\convert value
return ""
if fade
-- multiplies every alpha tag by a scaling factor determined by the fade envelope
local fadeFactor
-- Piecewise function done with logicals :)
-- probably should go in fadeTag\interpolate, but I dunno how that whole section is supposed to work
f = { k, tonumber v for k, v in pairs fade }
fadeFactor = (
(timeDelta < f.t1) and f.a1 or
(timeDelta < f.t2) and f.a1 + (f.a2 - f.a1) * (timeDelta - f.t1) / (f.t2 - f.t1) or
(timeDelta < f.t3) and f.a2 or
(timeDelta < f.t4) and f.a2 + (f.a3 - f.a2) * (timeDelta - f.t3) / (f.t4 - f.t3) or
f.a3
)
-- factor between 0 and 1 representing opacity of fade line
fadeFactor = (255 - fadeFactor) / 255
-- apply the opacity factor to all alpha tags
newText = newText\gsub "({.-})", ( tagBlock ) -> tagBlock\gsub "(\\[1234]?a[lpha]-)&H(%x%x)&", ( alpha, value ) ->
value = Math.round( 255 - ( fadeFactor * (255 - tonumber value, 16) ) )
return alpha .. "&H%02X&"\format value
else
-- modified each fade tag in every override block
newText = newText\gsub "({.-})", ( tagBlock ) -> tagBlock\gsub fadeTag.pattern, ( value ) ->
fade = fadeTag\convert value
-- Erroneous fade tags are being ignored and left sitting around as long as they doesn't have 2 or 7 arguments.
-- if t1 == nil
-- message = "There is a malformed \\fade you must fix.\n\\fade requires 7 integer arguments.\nLine: #{.number}, tag: \\fade(#{fade})."
-- if fade\match("(%d+),(%d+)")
-- message ..= "\nPerhaps you meant to use \\fad."
-- log.windowError message
for i = 4, 7 -- t1 - t4
fade[i] -= timeDelta
return fadeTag\format fade
if .move
newText = newText\gsub moveTag.pattern, ->
move = .move
progress = (timeDelta - move.start)/(move.end - move.start)
return posTag\format moveTag\interpolate {move.x1, move.y1}, {move.x2, move.y2}, progress
-- In theory, this is more optimal if we loop over the frames on
-- the outside loop and over the lines on the inside loop, as
-- this only needs to be calculated once for each frame, whereas
-- currently it is being calculated for each frame for each
-- line. However, if the loop structure is changed, then
-- inserting lines into the resultingCollection would need to be
-- more clever to compensate for the fact that lines would no
-- longer be added to it in order.
@lineTrackingData\calculateCurrentState frame
-- iterate through the necessary operations
for pattern, callback in pairs @callbacks
newText = newText\gsub pattern, ( tag, value ) ->
tag .. callback @, value, frame
newLine = Line line, @resultingCollection, {
text: newText,
start_time: newStartTime,
end_time: newEndTime,
transformsAreTokenized: false,
}
newLine.karaokeShift = (newStartTime - .start_time)*0.1
@resultingCollection\addLine newLine, nil, true, true
position = ( pos, frame ) =>
x, y = pos\match "([%-%d%.]+),([%-%d%.]+)"
x, y = positionMath x, y, @lineTrackingData
("(%g,%g)")\format Math.round( x, 2 ), Math.round( y, 2 )
positionMath = ( x, y, data ) ->
x = (tonumber( x ) - data.xStartPosition)*data.xRatio
y = (tonumber( y ) - data.yStartPosition)*data.yRatio
radius = math.sqrt( x^2 + y^2 )
alpha = Math.dAtan( y, x )
x = data.xCurrentPosition + radius*Math.dCos( alpha - data.zRotationDiff )
y = data.yCurrentPosition + radius*Math.dSin( alpha - data.zRotationDiff )
return x, y
absolutePosition = ( pos, frame ) =>
x, y = pos\match "([%-%d%.]+),([%-%d%.]+)"
@xDelta = @lineTrackingData.xPosition[frame] - x
@yDelta = @lineTrackingData.yPosition[frame] - y
("(%g,%g)")\format Math.round( @lineTrackingData.xPosition[frame], 2 ), Math.round( @lineTrackingData.yPosition[frame], 2 )
-- Needs to be fixed.
origin = ( origin, frame ) =>
ox, oy = origin\match("([%-%d%.]+),([%-%d%.]+)")
ox, oy = positionMath ox, oy, @lineTrackingData
("(%g,%g)")\format Math.round( ox, 2 ), Math.round( oy, 2 )
scale = ( scale, frame ) =>
scale *= @lineTrackingData.xRatio
tostring Math.round scale, 2
blur = ( blur, frame ) =>
ratio = @lineTrackingData.xRatio
ratio = 1 - (1 - ratio)*@options.main.blurScale
tostring Math.round blur*ratio, 2
rotate = ( rotation, frame ) =>
rotation += @lineTrackingData.zRotationDiff
tostring Math.round rotation, 2
rectangularClip = ( clip, frame ) =>
@rectClipData\calculateCurrentState frame
@rectClipData.zRotationDiff = 0
return clip\gsub "([%.%d%-]+),([%.%d%-]+)", ( x, y ) ->
x, y = x + @xDelta, y + @yDelta
x, y = positionMath x, y, @rectClipData
("%g,%g")\format Math.round( x, 2 ), Math.round( y, 2 )
vectorClip = ( clip, frame ) =>
-- This is redundant if vectClipData is the same as
-- lineTrackingData.
@vectClipData\calculateCurrentState frame
return clip\gsub "([%.%d%-]+) ([%.%d%-]+)", ( x, y ) ->
x, y = x + @xDelta, y + @yDelta
x, y = positionMath x, y, @vectClipData
("%g %g")\format Math.round( x, 2 ), Math.round( y, 2 )
vectorClipSRS = ( clip, frame ) =>
return '(' .. clip .. ' ' .. @vectClipData.data[frame]\sub( 1, -2 ) .. ')'
if haveDepCtrl
return version\register MotionHandler
else
return MotionHandler

View file

@ -1,130 +0,0 @@
local log
version = '1.0.2'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'ShakeShapeHandler'
:version
description: 'A class for parsing shake shape motion data.'
author: 'torque'
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.ShakeShapeHandler'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'a-mo.Log', version: '1.0.0' }
}
}
log = version\requireModules!
else
log = require 'a-mo.Log'
class ShakeShapeHandler
@version: version
new: ( input, @scriptHeight ) =>
if input
unless @parseRawDataString input
@parseFile input
if @rawData
@createDrawings!
parseRawDataString: ( rawDataString ) =>
if rawDataString\match "^shake_shape_data 4.0"
@tableize rawDataString
return true
return false
parseFile: ( fileName ) =>
if fileName\match "^\"[^\"]-\"$"
fileName = fileName\sub 2, -2
if file = io.open fileName, 'r'
return @parseRawDataString file\read '*a'
return false
tableize: ( rawDataString ) =>
shapes = rawDataString\match "num_shapes (%d+)"
@rawData = { }
rawDataString\gsub "([^\r\n]+)", ( line ) ->
if line\match "vertex_data"
table.insert @rawData, line
@numShapes = tonumber shapes
@length = #@rawData / @numShapes
createDrawings: =>
@data = { }
for baseIndex = 1, @length
results = { }
for curveIndex = baseIndex, @numShapes*@length, @length
line = @rawData[curveIndex]
table.insert results, convertVertex line, @scriptHeight
table.insert @data, table.concat results, ' '
fields = { "vx", "vy", "lx", "ly", "rx", "ry" }
updateCurve = ( curve, height, args ) ->
for index = 1, 6
field = fields[index]
if index % 2 == 0
curve[field] = height - args[index]
else
curve[field] = args[index]
processSegment = ( curveState, prevCurve, currCurve ) ->
result = ''
if ( prevCurve.vx == prevCurve.rx and prevCurve.vy == prevCurve.ry and
currCurve.lx == currCurve.vx and currCurve.ly == currCurve.vy )
unless curveState == 'l'
curveState = 'l'
result ..= 'l '
return curveState, result .. "#{currCurve.vx} #{currCurve.vy} "
else
unless curveState == 'b'
curveState = 'b'
result ..= 'b '
return curveState, result .. "#{prevCurve.rx} #{prevCurve.ry} #{currCurve.lx} #{currCurve.ly} #{currCurve.vx} #{currCurve.vy} "
convertVertex = ( vertex, scriptHeight ) ->
curveState = 'm'
drawString = {'m '}
prevCurve = { }
currCurve = { }
vertex = vertex\gsub "vertex_data ([%-%.%d]+) ([%-%.%d]+) ([%-%.%d]+) ([%-%.%d]+) ([%-%.%d]+) ([%-%.%d]+) [%-%.%d]+ [%-%.%d]+ [%-%.%d]+ [%-%.%d]+ [%-%.%d]+ [%-%.%d]+", ( ... ) ->
updateCurve prevCurve, scriptHeight, { ... }
table.insert drawString, "#{prevCurve.vx} #{prevCurve.vy} "
return ""
firstCurve = { k, v for k, v in pairs prevCurve }
vertex\gsub "([%-%.%d]+) ([%-%.%d]+) ([%-%.%d]+) ([%-%.%d]+) ([%-%.%d]+) ([%-%.%d]+) [%-%.%d]+ [%-%.%d]+ [%-%.%d]+ [%-%.%d]+ [%-%.%d]+ [%-%.%d]+", ( ... ) ->
updateCurve currCurve, scriptHeight, { ... }
curveState, segment = processSegment curveState, prevCurve, currCurve
table.insert drawString, segment
prevCurve, currCurve = currCurve, prevCurve
curveState, segment = processSegment curveState, prevCurve, firstCurve
table.insert drawString, segment
return table.concat drawString
-- A function stub because I am too lazy to do this sort of thing
-- properly.
calculateCurrentState: =>
checkLength: ( totalFrames ) =>
if totalFrames == @length
true
else
false
if haveDepCtrl
return version\register ShakeShapeHandler
else
return ShakeShapeHandler

View file

@ -1,153 +0,0 @@
local json, log
version = '0.1.3'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'Statistics'
:version
description: 'A class for proving how cool you are.'
author: 'torque'
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.Statistics'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'json' }
{ 'a-mo.Log', version: '1.0.0' }
}
}
json, log = version\requireModules!
else
json = require 'json'
log = require 'a-mo.Log'
-- example = {
-- macroRunCount: {
-- Apply: 0
-- Trim: 0
-- Revert: 0
-- }
-- longestLine: 0
-- longestTrack: 0
-- largestOutput: 0
-- totalProduced: 0
-- uuid: 0
-- }
-- No way to migrate to different layouts. Seems like a pain in the ass.
-- Probably won't get implemented.
class Statistics
@version: version
new: ( @stats, fileName, filePath = aegisub.decode_path( '?user' ) ) =>
@fileName = ('%s/%s')\format filePath, fileName
@read!
merge = ( memory, disk, seenTables ) ->
unless seenTables[memory]
seenTables[memory] = true
for k, memVal in pairs memory
if ("table" == type( disk )) and (nil != disk[k])
diskVal = disk[k]
if ("table" == type( diskVal )) and ("table" == type( memVal ))
merge memVal, diskVal, seenTables
else
memory[k] = diskVal
read: =>
if fileHandle = io.open @fileName, 'r'
success, serializedStats = pcall json.decode, fileHandle\read '*a'
fileHandle\close!
unless success
log.warn "Couldn't parse stats from #{@filename} as valid json. This file will be overwritten."
@write!
return
if serializedStats
merge @stats, serializedStats, {}
else
@write!
write: =>
if fileHandle = io.open @fileName, 'w'
serializedStats = json.encode @stats
if serializedStats
fileHandle\write serializedStats
else
log.debug "Couldn't serialize stats."
fileHandle\close!
else
log.debug "Can't write statsfile: #{@fileName}"
fullFieldNamePriv = false
fieldBaseNamePriv = false
fieldNamePriv = false
fieldPriv = false
pushFieldPriv = ( fieldName ) =>
-- primary cache: nothing needs to change.
if fullFieldNamePriv == fieldName
return
-- secondary cache: fieldPriv doesn't need to change, nor does
-- fieldBaseNamePriv, but fullFieldNamePriv and fieldNamePriv do.
tempFieldName = fieldName\gsub ".+%.", ""
if fieldBaseNamePriv == fieldName\sub 0, -(#tempFieldName + 2)
fullFieldNamePriv = fieldName
fieldNamePriv = tempFieldName
return
-- Have to do everything from scratch.
fieldPriv = @stats
done = false
fieldNamePriv = fieldName\gsub "([^%.]+)%.", ( subName ) ->
if done
return nil
if "table" != type fieldPriv[subName]
done = true
return nil
fieldPriv = fieldPriv[subName]
return ""
-- Bad things will occur if fieldNamePriv has a '.' in it.
fullFieldNamePriv = fieldName
fieldBaseNamePriv = fieldName\sub 0, -(#fieldNamePriv + 2)
-- Accept syntax like 'macroRunCount.Apply' for fieldName.
-- `valueCb` is a function with the signature ( currentValue )
setValuePriv = ( fieldName, valueCb ) =>
pushFieldPriv @, fieldName
fieldPriv[fieldNamePriv] = valueCb fieldPriv[fieldNamePriv]
incrementValue: ( fieldName, amount = 1 ) =>
setValuePriv @, fieldName, ( value ) ->
value + amount
-- Convenience.
decrementValue: ( fieldName, amount = 1 ) =>
setValuePriv @, fieldName, ( value ) ->
value - amount
setValue: ( fieldName, newValue ) =>
setValuePriv @, fieldName, ( value ) ->
newValue
setMax: ( fieldName, amount ) =>
setValuePriv @, fieldName, ( value ) ->
math.max value, amount
setMin: ( fieldName, amount ) =>
setValuePriv @, fieldName, ( value ) ->
math.min value, amount
getValue: ( fieldName ) =>
pushFieldPriv @, fieldName
return fieldPriv[fieldNamePriv]
if haveDepCtrl
return version\register Statistics
else
return Statistics

View file

@ -1,193 +0,0 @@
local log, Transform
version = '1.3.4'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'Tags'
:version
description: 'A mess for manipulating tags.'
author: 'torque'
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.Tags'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'a-mo.Log', version: '1.0.0' }
{ 'a-mo.Transform', version: '1.2.3' }
}
}
log, Transform = version\requireModules!
else
log = require 'a-mo.Log'
-- In the following conversion functions, self refers to the tag table.
convertStringValue = ( value ) =>
return value
convertNumberValue = ( value ) =>
return tonumber value
convertHexValue = ( value ) =>
return tonumber value, 16
convertColorValue = ( value ) =>
output = { }
for i = 1, 5, 2
table.insert output, tonumber value\sub( i, i+1 ), 16
output.r = output[3]
output.b = output[1]
output.g = output[2]
return output
convertKaraoke = ( ... ) =>
args = {...}
@tag = args[1]
return tonumber args[2]
-- This doesn't actually work with vector clips but i dont care.
convertMultiValue = ( value ) =>
output = { }
value\gsub "[%.%d%-]+", ( coord ) ->
table.insert output, coord
for index = 1, #@fieldnames
output[@fieldnames[index]] = output[index]
return output
convertTransformValue = ( value ) =>
-- awkwardly solve circular require.
Transform = Transform or require 'a-mo.Transform'
return Transform\fromString value
interpolateNumber = ( before, after, progress ) =>
return (1 - progress)*before + progress*after
interpolateMulti = ( before, after, progress ) =>
result = { }
for index = 1, #@fieldnames
key = @fieldnames[index]
result[index] = interpolateNumber @, before[index], after[index], progress
result[key] = result[index]
return result
interpolatePosition = ( before, after, progress ) =>
return {
interpolateNumber @, before[1], after[1], progress
interpolateNumber @, before[2], after[2], progress
}
interpolateColor = ( before, after, progress ) =>
return interpolateMulti { fieldnames: { 'b', 'g', 'r' } }, before, after, progress
formatString = ( value ) =>
return @tag .. value
formatInt = ( value ) =>
return ("%s%d")\format @tag, value
formatFloat = ( value ) =>
return ("%s%g")\format @tag, value
formatAlpha = ( alpha ) =>
return ("%s&H%02X&")\format @tag, alpha
formatColor = ( color ) =>
return ("%s&H%02X%02X%02X&")\format @tag, color.b, color.g, color.r
formatKaraoke = ( time ) =>
result = ("%s%d")\format @tag, time
return result
formatTransform = ( transform ) =>
return transform\toString!
formatMulti = ( value ) =>
return ("%s(%s)")\format @tag, table.concat value, ','
allTags = {
fontName: { pattern: "\\fn([^\\}]+)" , tag: "\\fn" , format: formatString, style: "fontname" , convert: convertStringValue }
fontSize: { pattern: "\\fs(%d+)" , tag: "\\fs" , format: formatInt , style: "fontsize", transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
fontSp: { pattern: "\\fsp([%.%d%-]+)" , tag: "\\fsp" , format: formatFloat , style: "spacing" , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
xscale: { pattern: "\\fscx([%d%.]+)" , tag: "\\fscx" , format: formatFloat , style: "scale_x" , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
yscale: { pattern: "\\fscy([%d%.]+)" , tag: "\\fscy" , format: formatFloat , style: "scale_y" , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
zrot: { pattern: "\\frz?([%-%d%.]+)" , tag: "\\frz" , format: formatFloat , style: "angle" , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
xrot: { pattern: "\\frx([%-%d%.]+)" , tag: "\\frx" , format: formatFloat , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
yrot: { pattern: "\\fry([%-%d%.]+)" , tag: "\\fry" , format: formatFloat , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
border: { pattern: "\\bord([%d%.]+)" , tag: "\\bord" , format: formatFloat , style: "outline" , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
xborder: { pattern: "\\xbord([%d%.]+)" , tag: "\\xbord", format: formatFloat , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
yborder: { pattern: "\\ybord([%d%.]+)" , tag: "\\ybord", format: formatFloat , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
shadow: { pattern: "\\shad([%-%d%.]+)" , tag: "\\shad" , format: formatFloat , style: "shadow" , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
xshadow: { pattern: "\\xshad([%-%d%.]+)", tag: "\\xshad", format: formatFloat , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
yshadow: { pattern: "\\yshad([%-%d%.]+)", tag: "\\yshad", format: formatFloat , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
reset: { pattern: "\\r([^\\}]*)" , tag: "\\r" , format: formatString , convert: convertStringValue }
alpha: { pattern: "\\alpha&H(%x%x)&" , tag: "\\alpha", format: formatAlpha , transformable: true , convert: convertHexValue , interpolate: interpolateNumber, type: "alpha" }
alpha1: { pattern: "\\1a&H(%x%x)&" , tag: "\\1a" , format: formatAlpha , style: "color1" , transformable: true , convert: convertHexValue , interpolate: interpolateNumber, type: "alpha", affectedBy: { "alpha" } }
alpha2: { pattern: "\\2a&H(%x%x)&" , tag: "\\2a" , format: formatAlpha , style: "color2" , transformable: true , convert: convertHexValue , interpolate: interpolateNumber, type: "alpha", affectedBy: { "alpha" } }
alpha3: { pattern: "\\3a&H(%x%x)&" , tag: "\\3a" , format: formatAlpha , style: "color3" , transformable: true , convert: convertHexValue , interpolate: interpolateNumber, type: "alpha", affectedBy: { "alpha" } }
alpha4: { pattern: "\\4a&H(%x%x)&" , tag: "\\4a" , format: formatAlpha , style: "color4" , transformable: true , convert: convertHexValue , interpolate: interpolateNumber, type: "alpha", affectedBy: { "alpha" } }
color1: { pattern: "\\1?c&H(%x+)&" , tag: "\\1c" , format: formatColor , style: "color1" , transformable: true , convert: convertColorValue , interpolate: interpolateColor , type: "color" }
color2: { pattern: "\\2c&H(%x+)&" , tag: "\\2c" , format: formatColor , style: "color2" , transformable: true , convert: convertColorValue , interpolate: interpolateColor , type: "color" }
color3: { pattern: "\\3c&H(%x+)&" , tag: "\\3c" , format: formatColor , style: "color3" , transformable: true , convert: convertColorValue , interpolate: interpolateColor , type: "color" }
color4: { pattern: "\\4c&H(%x+)&" , tag: "\\4c" , format: formatColor , style: "color4" , transformable: true , convert: convertColorValue , interpolate: interpolateColor , type: "color" }
be: { pattern: "\\be([%d%.]+)" , tag: "\\be" , format: formatInt , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
blur: { pattern: "\\blur([%d%.]+)" , tag: "\\blur" , format: formatFloat , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
xshear: { pattern: "\\fax([%-%d%.]+)" , tag: "\\fax" , format: formatFloat , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
yshear: { pattern: "\\fay([%-%d%.]+)" , tag: "\\fay" , format: formatFloat , transformable: true , convert: convertNumberValue, interpolate: interpolateNumber }
align: { pattern: "\\an([1-9])" , tag: "\\an" , format: formatInt , style: "align" , convert: convertNumberValue , global: true }
-- bold, italic, underline and strikeout are actually stored in the style table as boolean values.
bold: { pattern: "\\b(%d+)" , tag: "\\b" , format: formatInt , style: "bold" , convert: convertNumberValue }
underline:{ pattern: "\\u([01])" , tag: "\\u" , format: formatInt , style: "underline" , convert: convertNumberValue }
italic: { pattern: "\\i([01])" , tag: "\\i" , format: formatInt , style: "italic" , convert: convertNumberValue }
strike: { pattern: "\\s([01])" , tag: "\\s" , format: formatInt , style: "strikeout" , convert: convertNumberValue }
drawing: { pattern: "\\p(%d+)" , tag: "\\p" , format: formatInt , convert: convertNumberValue }
transform:{ pattern: "\\t(%(.-%))" , tag: "\\t" , format: formatTransform , convert: convertTransformValue }
karaoke: { pattern: "(\\[kK][fo]?)(%d+)" , format: formatInt , convert: convertKaraoke }
-- Problematic tags:
pos: { fieldnames: { "x", "y" } , output: "multi", pattern: "\\pos%(([%.%d%-]+,[%.%d%-]+)%)", tag: "\\pos" , format: formatMulti, convert: convertMultiValue, global: true }
org: { fieldnames: { "x", "y" } , output: "multi", pattern: "\\org%(([%.%d%-]+,[%.%d%-]+)%)", tag: "\\org" , format: formatMulti, convert: convertMultiValue, global: true }
fad: { fieldnames: { "in", "out" } , output: "multi", pattern: "\\fade?%((%d+,%d+)%)" , tag: "\\fad" , format: formatMulti, convert: convertMultiValue, global: true }
vectClip: { fieldnames: { "scale", "shape" }, output: "multi", pattern: "\\clip%((%d+,)?([^,]-)%)" , tag: "\\clip" , format: formatMulti, convert: convertMultiValue, global: true }
vectiClip:{ fieldnames: { "scale", "shape" }, output: "multi", pattern: "\\iclip%((%d+,)?([^,]-)%)" , tag: "\\iclip", format: formatMulti, convert: convertMultiValue, global: true }
rectClip: { fieldnames: { "xLeft", "yTop", "xRight", "yBottom" } , output: "multi", pattern: "\\clip%(([%-%d%.]+,[%-%d%.]+,[%-%d%.]+,[%-%d%.]+)%)?" , transformable: true, tag: "\\clip" , format: formatMulti, convert: convertMultiValue, interpolate: interpolateMulti, global: true }
rectiClip:{ fieldnames: { "xLeft", "yTop", "xRight", "yBottom" } , output: "multi", pattern: "\\iclip%(([%-%d%.]+,[%-%d%.]+,[%-%d%.]+,[%-%d%.]+)%)?", transformable: true, tag: "\\iclip", format: formatMulti, convert: convertMultiValue, interpolate: interpolateMulti, global: true }
move: { fieldnames: { "x1", "y1", "x2", "y2", "start", "end" } , output: "multi", pattern: "\\move%(([%.%d%-]+,[%.%d%-]+,[%.%d%-]+,[%.%d%-]+,[%d%-]+,[%d%-]+)%)" , tag: "\\move" , format: formatMulti, convert: convertMultiValue, interpolate: interpolatePosition, global: true }
fade: { fieldnames: { "a1", "a2", "a3", "t1", "t2", "t3", "t4" }, output: "multi", pattern: "\\fade%((%d+,%d+,%d+,[%d%-]+,[%d%-]+,[%d%-]+,[%d%-]+)%)" , tag: "\\fade" , format: formatMulti, convert: convertMultiValue, global: true }
}
repeatTags = { }
oneTimeTags = { }
styleTags = { }
transformTags = { }
for k, v in pairs allTags
v.name = k
unless v.global
table.insert repeatTags, v
else
table.insert oneTimeTags, v
if v.style
table.insert styleTags, v
if v.transformable
table.insert transformTags, v
tags = {
:version
:repeatTags
:oneTimeTags
:styleTags
:transformTags
:allTags
}
if haveDepCtrl
return version\register tags
else
return tags

View file

@ -1,143 +0,0 @@
local log, Math, tags
version = '1.2.4'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'Transform'
:version
description: 'A class for managing the transform tag.'
author: 'torque'
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.Transform'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'a-mo.Log', version: '1.0.0' }
{ 'a-mo.Math', version: '1.0.0' }
{ 'a-mo.Tags', version: '1.3.4' }
}
}
log, Math, tags = version\requireModules!
else
log = require 'a-mo.Log'
Math = require 'a-mo.Math'
class Transform
@version: version
tags = tags or require 'a-mo.Tags'
-- An alternate constructor.
@fromString: ( transformString, lineDuration, tagIndex, parentLine ) =>
transStart, transEnd, transExp, transEffect = transformString\match "%(([%-%d]*),?([%-%d]*),?([%d%.]*),?(.+)%)"
-- Catch the case of \t(2.345,\1c&H0000FF&), where the 2 gets
-- matched to transStart and the .345 gets matched to transEnd.
if tonumber( transStart ) and not tonumber( transEnd )
transExp = transStart .. transExp
transStart = ""
transExp = tonumber( transExp ) or 1
transStart = tonumber( transStart ) or 0
transEnd = tonumber( transEnd ) or 0
if transEnd == 0
transEnd = lineDuration
object = @ transStart, transEnd, transExp, transEffect, tagIndex, parentLine
object.rawString = transformString
return object
new: ( @startTime, @endTime, @accel, @effect, @index, @parentLine ) =>
@gatherTagsInEffect!
__tostring: => return @toString!
toString: ( line = @parentLine ) =>
if @effect == ""
return ""
elseif @endTime <= 0
return @effect
elseif @startTime > line.duration or @endTime < @startTime
return ""
elseif @accel == 1
return ("\\t(%s,%s,%s)")\format @startTime, @endTime, @effect
else
return ("\\t(%s,%s,%s,%s)")\format @startTime, @endTime, @accel, @effect
gatherTagsInEffect: =>
if @effectTags
return
@effectTags = { }
for tag in *tags.transformTags
@effect\gsub tag.pattern, ( value ) ->
log.debug "Found tag: %s -> %s", tag.name, value
unless @effectTags[tag]
@effectTags[tag] = { }
endValue = tag\convert value
table.insert @effectTags[tag], endValue
@effectTags[tag].last = endValue
collectPriorState: ( line, text, placeholder ) =>
-- Fill out all of the relevant tag defaults. This works great for
-- everything except \clip, which defaults to 0, 0, width, height
@priorValues = { }
for tag, _ in pairs @effectTags
if tag.style
@priorValues[tag] = line.properties[tag]
else
@priorValues[tag] = 0
if @effectTags[tags.allTags.rectClip]
@priorValues[tags.allTags.rectClip] = { 0, 0, line.parentCollection.meta.PlayResX, line.parentCollection.meta.PlayResY }
if @effectTags[tags.allTags.rectiClip]
@priorValues[tags.allTags.rectiClip] = { 0, 0, line.parentCollection.meta.PlayResX, line.parentCollection.meta.PlayResY }
i = 1
text\gsub "({.-})", ( tagBlock ) ->
for tag, _ in pairs @effectTags
if tag.affectedBy
newTagBlock = tagBlock\gsub ".-"..tag.pattern, ( value ) ->
@priorValues[tag] = tag\convert value
return ""
for tagName in *tag.affectedBy
newTag = tags.allTags[tagName]
newTagBlock = newTagBlock\gsub ".-"..newTag.pattern, ( value ) ->
@priorValues[tag] = newTag\convert value
return ""
else
tagBlock\gsub tag.pattern, ( value ) ->
@priorValues[tag] = tag\convert value
i += 1
return nil,
@index
interpolate: ( line, text, placeholder, time ) =>
@collectPriorState line, text, placeholder
linearProgress = (time - @startTime)/(@endTime - @startTime)
progress = math.pow linearProgress, @accel
text = text\gsub placeholder, ->
resultString = {}
for tag, endValues in pairs @effectTags
if linearProgress <= 0
table.insert resultString, tag\format @priorValues[tag]
elseif linearProgress >= 1
table.insert resultString, tag\format endValues.last
else
value = @priorValues[tag]
for endValue in *endValues
value = tag\interpolate value, endValue, progress
table.insert resultString, tag\format value
return table.concat resultString
return text
if haveDepCtrl
return version\register Transform
else
return Transform

View file

@ -1,185 +0,0 @@
local log
version = '1.0.5'
haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
if haveDepCtrl
version = DependencyControl {
name: 'TrimHandler'
:version
description: 'A class for encoding video clips.'
author: 'torque'
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
moduleName: 'a-mo.TrimHandler'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
{ 'a-mo.Log', version: '1.0.0' }
}
}
log = version\requireModules!
else
log = require 'a-mo.Log'
windows = jit.os == "Windows"
class TrimHandler
@version: version
@windows: windows
existingPresets: {
"x264", "ffmpeg"
}
-- Set up encoder presets.
defaults: {
x264: '"#{encbin}" --crf 16 --tune fastdecode -i 250 --fps 23.976 --sar 1:1 --index "#{prefix}/#{index}.index" --seek #{startf} --frames #{lenf} -o "#{prefix}/#{output}[#{startf}-#{endf}].mp4" "#{inpath}/#{input}"'
ffmpeg: '"#{encbin}" -ss #{startt} -an -sn -i "#{inpath}/#{input}" -q:v 1 -vsync passthrough -frames:v #{lenf} "#{prefix}/#{output}[#{startf}-#{endf}]-%05d.jpg"'
-- avs2pipe: 'echo FFVideoSource("#{inpath}#{input}",cachefile="#{prefix}#{index}.index").trim(#{startf},#{endf}).ConvertToRGB.ImageWriter("#{prefix}/#{output}-[#{startf}-#{endf}]\\",type="png").ConvertToYV12 > "#{temp}/a-mo.encode.avs"\nmkdir "#{prefix}#{output}-[#{startf}-#{endf}]"\n"#{encbin}" video "#{temp}/a-mo.encode.avs"\ndel "#{temp}/a-mo.encode.avs"'
-- vapoursynth:
}
-- Example trimConfig:
-- trimConfig = {
-- -- The prefix is the directory the output will be written to. It
-- -- is passed through aegisub.decode_path.
-- prefix: "?video"
-- -- The name of the built in encoding preset to use. Overridden by
-- -- command if that is neither nil nor an empty string.
-- preset: "x264"
-- -- The path of the executable used to actually do the encoding.
-- -- Full path is recommended as the shell environment may be
-- -- different than expected on non-windows systems.
-- encBin: "C:\x264.exe"
-- -- A custom encoding command that can be used to override the
-- -- built-in defaults. Usable token documentation to come.
-- -- Overrides preset if that is set.
-- command: nil
-- -- Script should attempt to create prefix directory.
-- makePfix: nil
-- -- Script should attempt to log output of the encoding command.
-- writeLog: true
-- }
new: ( trimConfig ) =>
@tokens = { }
if trimConfig.command != nil
trimConfig.command = trimConfig.command\gsub "[\t \r\n]*$", ""
if trimConfig.command != ""
@command = trimConfig.command
else
@command = @defaults[trimConfig.preset]
else
@command = @defaults[trimConfig.preset]
@makePrefix = trimConfig.makePfix
@writeLog = trimConfig.writeLog
with @tokens
.temp = aegisub.decode_path "?temp"
-- For some reason, aegisub appends / to the end of ?temp but not
-- other tokens.
finalTemp = .temp\sub -1, -1
if finalTemp == '\\' or finalTemp == '/'
.temp = .temp\sub 1, -2
.encbin = trimConfig.encBin
.prefix = aegisub.decode_path trimConfig.prefix
.inpath = aegisub.decode_path "?video"
.log = aegisub.decode_path "#{.temp}/a-mo.encode.log"
getVideoName @
getVideoName = =>
with @tokens
video = aegisub.project_properties!.video_file
if video\len! == 0
log.windowError "Aegisub thinks your video is 0 frames long.\nTheoretically it should be impossible to get this error."
if video\match "^?dummy"
log.windowError "I can't encode that dummy video for you."
.input = video\gsub( "^[A-Z]:\\", "", 1 )\gsub ".+[^\\/]-[\\/]", "", 1
.index = .input\match "(.+)%.[^%.]+$"
.output = .index
calculateTrimLength: ( lineCollection ) =>
with @tokens
.startt = lineCollection.startTime / 1000
.endt = lineCollection.endTime / 1000
.lent = .endt - .startt
.startf = lineCollection.startFrame
.endf = lineCollection.endFrame - 1
.lenf = lineCollection.totalFrames
performTrim: =>
with platform = ({
[true]: {
pre: @tokens.temp
ext: ".ps1"
-- This needs to be run from cmd or it will not work.
exec: 'powershell -c iex "$(gc "%s" -en UTF8)"'
preCom: (@makePrefix and "mkdir -Force \"#{@tokens.prefix}\"; & " or "& ")
postCom: (@writeLog and " 2>&1 | Out-File #{@tokens.log} -en UTF8; if($LASTEXITCODE -ne 0) {echo \"If there is no log before this, your encoder is not a working executable or your encoding command is invalid.\" | ac -en utf8 #{@tokens.log}; exit 1}" or "") .. "; exit 0"
execFunc: ( encodeScript ) ->
-- clear out old logfile because it doesn't get overwritten
-- when certain errors occur.
if @writeLog
logFile = io.open @tokens.log, 'wb'
logFile\close! if logFile
success = os.execute encodeScript
if @writeLog and not success
logFile = io.open @tokens.log, 'r'
unless logFile
log.windowError "Could not read log file #{@tokens.log}.\nSomething has gone horribly wrong."
encodeLog = logFile\read '*a'
logFile\close!
log.warn "\nEncoding error:"
log.warn encodeLog
log.windowError "Encoding failed. Log has been printed to progress window."
elseif not success
log.windowError "Encoding seems to have failed but you didn't write a log file."
}
[false]: {
pre: @tokens.temp
ext: ".sh"
exec: 'sh "%s"'
preCom: @makePrefix and "mkdir -p \"#{@tokens.prefix}\"\n" or ""
postCom: " 2>&1; if [[ $? -ne 0 ]]; then echo \"If there is no log before this, your encoder is not a working executable or your encoding command is invalid.\"; false; fi"
execFunc: ( encodeScript ) ->
logFile = io.popen encodeScript, 'r'
encodeLog = logFile\read '*a'
-- When closing a file handle created with io.popen,
-- file:close returns the same values returned by
-- os.execute.
success = logFile\close!
unless success
log.warn "\nEncoding error:"
log.warn encodeLog
log.windowError "Encoding failed. Log has been printed to progress window."
}
})[windows]
-- check encoder binary exists
encoder = io.open @tokens.encbin, "rb"
unless encoder
log.windowError "Encoding binary (#{@tokens.encbin}) does not appear to exist."
encoder\close!
encodeScript = aegisub.decode_path "#{.pre}/a-mo.encode#{.ext}"
encodeScriptFile = io.open encodeScript, "w+"
unless encodeScriptFile
log.windowError "Encoding script could not be written.\nSomething is wrong with your temp dir (#{.pre})."
encodeString = .preCom .. @command\gsub( "#{(.-)}", ( token ) -> @tokens[token] ) .. .postCom
if windows
encodeString = encodeString\gsub "`", "``"
log.debug encodeString
encodeScriptFile\write encodeString
encodeScriptFile\close!
.execFunc .exec\format encodeScript
if haveDepCtrl
return version\register TrimHandler
else
return TrimHandler

File diff suppressed because it is too large Load diff

View file

@ -1,362 +0,0 @@
[[
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

View file

@ -1,387 +0,0 @@
DependencyControl = require "l0.DependencyControl"
version = DependencyControl {
name: "ASSParser",
version: "0.0.4",
description: "Utility function for parsing ASS files",
author: "Myaamori",
url: "http://github.com/TypesettingTools/Myaamori-Aegisub-Scripts",
moduleName: "myaa.ASSParser",
feed: "https://raw.githubusercontent.com/TypesettingTools/Myaamori-Aegisub-Scripts/master/DependencyControl.json",
{
"aegisub.re", "aegisub.util",
{"l0.Functional", version: "0.6.0", url: "https://github.com/TypesettingTools/Functional",
feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}
}
}
re, util, F = version\requireModules!
import lshift, rshift, band, bor from bit
parser = {}
STYLE_FORMAT_STRING = "Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, " ..
"OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, " ..
"Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, " ..
"MarginV, Encoding"
EVENT_FORMAT_STRING = "Layer, Start, End, Style, Name, MarginL, MarginR, " ..
"MarginV, Effect, Text"
DATA_FORMAT_STRING = "Id, Key, Value"
DIALOGUE_DEFAULTS =
actor: "", class: "dialogue", comment: false, effect: "",
start_time: 0, end_time: 0, layer: 0, margin_l: 0,
margin_r: 0, margin_t: 0, section: "[Events]", style: "Default",
text: "", extra: nil
STYLE_DEFAULTS =
class: "style", section: "[V4+ Styles]", name: "Default",
fontname: "Arial", fontsize: 45, color1: "&H00FFFFFF",
color2: "&H000000FF", color3: "&H00000000", color4: "&H00000000",
bold: false, italic: false, underline: false, strikeout: false,
scale_x: 100, scale_y: 100, spacing: 0, angle: 0,
borderstyle: 1, outline: 4.5, shadow: 4.5, align: 2,
margin_l: 23, margin_r: 23, margin_t: 23, encoding: 1
create_line_from = (line, fields)->
line = util.copy line
if fields
for key, value in pairs fields
line[key] = value
return line
parser.create_dialogue_line = (fields)->
line = create_line_from DIALOGUE_DEFAULTS, fields
line.extra = line.extra or {}
line
parser.create_style_line = (fields)-> create_line_from STYLE_DEFAULTS, fields
parser.decode_extradata_value = (value)->
enc, data = value\match "^([eu])(.*)$"
if enc == 'e'
return parser.inline_string_decode data
else
return parser.uudecode data
parse_format_line = (format_string)-> [match for match in format_string\gmatch "([^, ]+)"]
parser.raw_to_line = (raw, extradata=nil, format=nil)->
line_type, value = raw\match "^([^:]+):%s*(.*)$"
if not value
return nil
default_format = {Dialogue: EVENT_FORMAT_STRING,
Comment: EVENT_FORMAT_STRING,
Style: STYLE_FORMAT_STRING,
Data: DATA_FORMAT_STRING}
if line_type == "Format"
return {class: "format", format: parse_format_line value}
elseif not default_format[line_type]
return {class: "info", key: line_type, value: value}
format = format or parse_format_line default_format[line_type]
elements = F.string.split value, ",", 1, true, #format - 1
return nil if #elements != #format
fields = {format[i], elements[i] for i=1,#elements}
if line_type == "Dialogue" or line_type == "Comment"
line = parser.create_dialogue_line
actor: fields.Name, comment: line_type == "Comment"
effect: fields.Effect, start_time: F.util.assTimecode2ms(fields.Start)
end_time: F.util.assTimecode2ms(fields.End), layer: tonumber(fields.Layer)
margin_l: tonumber(fields.MarginL), margin_r: tonumber(fields.MarginR)
margin_t: tonumber(fields.MarginV), style: fields.Style
text: fields.Text
-- handle extradata (e.g. '{=32=33}Line text')
extramatch = re.match line.text, "^\\{((?:=\\d+)+)\\}(.*)$"
if extramatch
line.text = extramatch[3].str
if extradata
for id in extramatch[2].str\gmatch "=(%d+)"
id = tonumber id
if extradata[id]
eline = extradata[id]
line.extra[eline.key] = eline.value
else
aegisub.log 2,
"WARNING: Found extradata ID, but no extradata mapping provided: " ..
"#{raw}\n"
return line
elseif line_type == "Style"
boolean_map = {["-1"]: true, ["0"]: false}
line = parser.create_style_line
name: fields.Name, fontname: fields.Fontname
fontsize: tonumber(fields.Fontsize), color1: fields.PrimaryColour
color2: fields.SecondaryColour, color3: fields.OutlineColour
color4: fields.BackColour, bold: boolean_map[fields.Bold]
italic: boolean_map[fields.Italic], underline: boolean_map[fields.Underline]
strikeout: boolean_map[fields.StrikeOut], scale_x: tonumber(fields.ScaleX)
scale_y: tonumber(fields.ScaleY), spacing: tonumber(fields.Spacing)
angle: tonumber(fields.Angle), borderstyle: tonumber(fields.BorderStyle)
outline: tonumber(fields.Outline), shadow: tonumber(fields.Shadow)
align: tonumber(fields.Alignment), margin_l: tonumber(fields.MarginL)
margin_r: tonumber(fields.MarginR), margin_t: tonumber(fields.MarginV)
encoding: tonumber(fields.Encoding)
return line
elseif line_type == "Data"
return {class: "data", id: tonumber(fields.Id),
key: fields.Key, value: parser.decode_extradata_value fields.Value}
parser.line_to_raw = (line)->
if line.class == "dialogue"
prefix = if line.comment then "Comment" else "Dialogue"
"#{prefix}: #{line.layer},#{F.util.ms2AssTimecode line.start_time}," ..
"#{F.util.ms2AssTimecode line.end_time},#{line.style},#{line.actor}," ..
"#{line.margin_l},#{line.margin_r},#{line.margin_t},#{line.effect},#{line.text}"
elseif line.class == "style"
map = {[true]: "-1", [false]: "0"}
clr = (color)-> util.ass_style_color util.extract_color color
"Style: #{line.name},#{line.fontname},#{line.fontsize},#{clr line.color1}," ..
"#{clr line.color2},#{clr line.color3},#{clr line.color4},#{map[line.bold]}," ..
"#{map[line.italic]},#{map[line.underline]},#{map[line.strikeout]}," ..
"#{line.scale_x},#{line.scale_y},#{line.spacing},#{line.angle}," ..
"#{line.borderstyle},#{line.outline},#{line.shadow},#{line.align}," ..
"#{line.margin_l},#{line.margin_r},#{line.margin_t},#{line.encoding}"
elseif line.class == "info"
"#{line.key}: #{line.value}"
parser.inline_string_encode = (input)->
output = {}
for i=1,#input
c = input\byte i
if c <= 0x1F or c >= 0x80 or c == 0x23 or c == 0x2C or c == 0x3A or c == 0x7C
table.insert output, string.format "#%02X", c
else
table.insert output, input\sub i,i
return table.concat output
parser.inline_string_decode = (input)->
output = {}
i = 1
while i <= #input
if (input\sub i, i) != "#" or i + 1 > #input
table.insert output, input\sub i, i
else
table.insert output, string.char tonumber (input\sub i+1, i+2), 16
i += 2
i += 1
return table.concat output
parser.uuencode = (input)->
ret = {}
for pos=1,#input,3
chunk = input\sub pos, pos+2
src = [c\byte! for c in chunk\gmatch "."]
while #src < 3
src[#src+1] = 0
dst = {(rshift src[1], 2),
(bor (lshift (band src[1], 0x3), 4), (rshift (band src[2], 0xF0), 4)),
(bor (lshift (band src[2], 0xF), 2), (rshift (band src[3], 0xC0), 6)),
(band src[3], 0x3F)}
for i=1,math.min(#input - pos + 2, 4)
table.insert ret, dst[i] + 33
return table.concat [string.char i for i in *ret]
parser.uudecode = (input)->
ret = {}
pos = 1
while pos <= #input
chunk = input\sub pos, pos+3
src = [(string.byte c) - 33 for c in chunk\gmatch "."]
if #src > 1
table.insert ret, bor (lshift src[1], 2), (rshift src[2], 4)
if #src > 2
table.insert ret, bor (lshift (band src[2], 0xF), 4), (rshift src[3], 2)
if #src > 3
table.insert ret, bor (lshift (band src[3], 0x3), 6), src[4]
pos += #src
return table.concat [string.char i for i in *ret]
class ASSFile
new: (file)=>
@sections = {}
@styles = {}
@events = {}
@script_info = {}
@script_info_mapping = {}
@aegisub_garbage = {}
@aegisub_garbage_mapping = {}
@extradata = {}
@extradata_mapping = {}
@parse file
parse: (file)=>
@read_sections file
@parse_extradata!
@script_info = @parse_section "Script Info", {"info": true}
@aegisub_garbage = @parse_section "Aegisub Project Garbage", {"info": true}
@styles = @parse_section "V4+ Styles", {"style": true}
@events = @parse_section "Events", {"dialogue": true}
for info in *@script_info
@script_info_mapping[info.key] = info.value
for garbage in *@aegisub_garbage
@aegisub_garbage_mapping[garbage.key] = garbage.value
read_sections: (file)=>
current_section = nil
-- read lines from file, sort into sections
for row in file\lines!
-- remove BOM if present, remove newlines, and trim leading spaces
row = F.string.trimLeft (row\gsub "^\xEF\xBB\xBF", "")\gsub "[\r\n]*$", ""
if row == "" or row\match "^;"
continue
section = row\match "^%[(.*)%]$"
if section
current_section = section
@sections[current_section] = {}
continue
table.insert @sections[current_section], row
parse_extradata: =>
if @sections["Aegisub Extradata"]
for row in *@sections["Aegisub Extradata"]
line = parser.raw_to_line row
if not line or line.class != "data"
aegisub.log 2, "WARNING: Malformed data line: #{row}\n"
continue
@extradata[line.id] = line
@extradata_mapping[line.key] = @extradata_mapping[line.key] or {}
@extradata_mapping[line.key][line.value] = line.id
parse_section: (section, expected_classes)=>
lines = {}
return lines if not @sections[section]
format = nil
for row in *@sections[section]
line = parser.raw_to_line row, @extradata, format
if not line
aegisub.log 2, "WARNING: Malformed line: #{line}\n"
elseif line.class == "format"
format = line.format
elseif expected_classes[line.class]
table.insert lines, line
else
aegisub.log 2, "WARNING: Unexpected type #{line.class} in section #{section}\n"
return lines
parser.parse_file = (file)->
return ASSFile file
parser.generate_styles_section = (styles, callback)->
callback "[V4+ Styles]\n"
callback "Format: #{STYLE_FORMAT_STRING}\n"
for line in *styles
callback parser.line_to_raw(line) .. "\n"
parser.generate_events_section = (events, extradata_mapping, callback)->
callback "[Events]\n"
callback "Format: #{EVENT_FORMAT_STRING}\n"
-- find the largest extradata ID seen so far
last_eid = 0
if extradata_mapping
for key, v in pairs extradata_mapping
for value, eid in pairs v
last_eid = math.max last_eid, eid
extradata_to_write = {}
for line in *events
-- handle extradata
if line.extra and extradata_mapping
lineindices = {}
for key, value in pairs line.extra
-- look for data in the original file's extradata
cached_id = extradata_mapping[key] and extradata_mapping[key][value]
if not cached_id
-- if new extradata, generate new ID and cache it
last_eid += 1
cached_id = last_eid
extradata_mapping[key] = extradata_mapping[key] or {}
extradata_mapping[key][value] = cached_id
table.insert lineindices, cached_id
extradata_to_write[cached_id] = {key, value}
-- add indices to line text (e.g. {=32=33}Text)
if #lineindices > 0
table.sort lineindices
indexstring = table.concat ["=#{ind}" for ind in *lineindices]
line.text = "{#{indexstring}}" .. line.text
callback parser.line_to_raw(line) .. "\n"
out_indices = [ind for ind, _ in pairs extradata_to_write]
if #out_indices > 0
callback "\n[Aegisub Extradata]\n"
table.sort out_indices
for ind in *out_indices
{key, value} = extradata_to_write[ind]
encoded_data = parser.inline_string_encode value
-- a mystical incantation passed down from subtitle_format_ass.cpp
if 4*#value < 3*#encoded_data
value = "u" .. parser.uuencode value
else
value = "e" .. encoded_data
callback "Data: #{ind},#{key},#{value}\n"
parser.generate_script_info_section = (lines, callback, bom=true)->
if bom
callback "\xEF\xBB\xBF"
callback "[Script Info]\n"
for line in *lines
callback parser.line_to_raw(line) .. "\n"
parser.generate_aegisub_garbage_section = (lines, callback)->
callback "[Aegisub Project Garbage]\n"
for line in *lines
callback parser.line_to_raw(line) .. "\n"
parser.generate_file = (script_info, aegisub_garbage, styles, events, extradata_mapping, callback)->
sec_added = false
new_section = ->
if sec_added
callback "\n"
sec_added = true
if script_info
new_section!
parser.generate_script_info_section script_info, callback
if aegisub_garbage
new_section!
parser.generate_aegisub_garbage_section aegisub_garbage, callback
if styles
new_section!
parser.generate_styles_section styles, callback
if events
new_section!
parser.generate_events_section events, extradata_mapping, callback
parser.version = version
return version\register parser

View file

@ -1,34 +0,0 @@
local DependencyControl = require("l0.DependencyControl")
local version = DependencyControl{
name = "Penlight",
version = "1.6.0",
description = "Python-inspired utility library.",
author = "stevedonovan",
url = "http://github.com/Myaamori/Penlight",
moduleName = "myaa.pl",
}
package.path = package.path .. ";" .. aegisub.decode_path("?user/automation/include/myaa/?.lua")
local modules_to_load = {
utils = true,path=true,dir=true,tablex=true,stringio=true,sip=true,
input=true,seq=true,lexer=true,stringx=true,
config=true,pretty=true,data=true,func=true,text=true,
operator=true,lapp=true,array2d=true,
comprehension=true,xml=true,types=true,
test = true, app = true, file = true, class = true,
luabalanced = true, permute = true, template = true,
url = true, compat = true, List = true, Map = true, Set = true,
OrderedMap = true, MultiMap = true, Date = true,
-- classes --
}
local modules = {}
for k, v in pairs(modules_to_load) do
modules[k] = require ("pl." .. k)
end
modules.version = version
return modules

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,113 +0,0 @@
--- A Map class.
--
-- > Map = require 'pl.Map'
-- > m = Map{one=1,two=2}
-- > m:update {three=3,four=4,two=20}
-- > = m == M{one=1,two=20,three=3,four=4}
-- true
--
-- Dependencies: `pl.utils`, `pl.class`, `pl.tablex`, `pl.pretty`
-- @classmod pl.Map
local tablex = require 'pl.tablex'
local utils = require 'pl.utils'
local stdmt = utils.stdmt
local deepcompare = tablex.deepcompare
local pretty_write = require 'pl.pretty' . write
local Map = stdmt.Map
local Set = stdmt.Set
local class = require 'pl.class'
-- the Map class ---------------------
class(nil,nil,Map)
function Map:_init (t)
local mt = getmetatable(t)
if mt == Set or mt == Map then
self:update(t)
else
return t -- otherwise assumed to be a map-like table
end
end
local function makelist(t)
return setmetatable(t, require('pl.List'))
end
--- list of keys.
Map.keys = tablex.keys
--- list of values.
Map.values = tablex.values
--- return an iterator over all key-value pairs.
function Map:iter ()
return pairs(self)
end
--- return a List of all key-value pairs, sorted by the keys.
function Map:items()
local ls = makelist(tablex.pairmap (function (k,v) return makelist {k,v} end, self))
ls:sort(function(t1,t2) return t1[1] < t2[1] end)
return ls
end
-- Will return the existing value, or if it doesn't exist it will set
-- a default value and return it.
function Map:setdefault(key, defaultval)
return self[key] or self:set(key,defaultval) or defaultval
end
--- size of map.
-- note: this is a relatively expensive operation!
-- @class function
-- @name Map:len
Map.len = tablex.size
--- put a value into the map.
-- This will remove the key if the value is `nil`
-- @param key the key
-- @param val the value
function Map:set (key,val)
self[key] = val
end
--- get a value from the map.
-- @param key the key
-- @return the value, or nil if not found.
function Map:get (key)
return rawget(self,key)
end
local index_by = tablex.index_by
--- get a list of values indexed by a list of keys.
-- @param keys a list-like table of keys
-- @return a new list
function Map:getvalues (keys)
return makelist(index_by(self,keys))
end
--- update the map using key/value pairs from another table.
-- @tab table
-- @function Map:update
Map.update = tablex.update
--- equality between maps.
-- @within metamethods
-- @tparam Map m another map.
function Map:__eq (m)
-- note we explicitly ask deepcompare _not_ to use __eq!
return deepcompare(self,m,true)
end
--- string representation of a map.
-- @within metamethods
function Map:__tostring ()
return pretty_write(self,'')
end
return Map

View file

@ -1,54 +0,0 @@
--- MultiMap, a Map which has multiple values per key.
--
-- Dependencies: `pl.utils`, `pl.class`, `pl.List`, `pl.Map`
-- @classmod pl.MultiMap
local utils = require 'pl.utils'
local class = require 'pl.class'
local List = require 'pl.List'
local Map = require 'pl.Map'
-- MultiMap is a standard MT
local MultiMap = utils.stdmt.MultiMap
class(Map,nil,MultiMap)
MultiMap._name = 'MultiMap'
function MultiMap:_init (t)
if not t then return end
self:update(t)
end
--- update a MultiMap using a table.
-- @param t either a Multimap or a map-like table.
-- @return the map
function MultiMap:update (t)
utils.assert_arg(1,t,'table')
if Map:class_of(t) then
for k,v in pairs(t) do
self[k] = List()
self[k]:append(v)
end
else
for k,v in pairs(t) do
self[k] = List(v)
end
end
end
--- add a new value to a key. Setting a nil value removes the key.
-- @param key the key
-- @param val the value
-- @return the map
function MultiMap:set (key,val)
if val == nil then
self[key] = nil
else
if not self[key] then
self[key] = List()
end
self[key]:append(val)
end
end
return MultiMap

View file

@ -1,167 +0,0 @@
--- OrderedMap, a map which preserves ordering.
--
-- Derived from `pl.Map`.
--
-- Dependencies: `pl.utils`, `pl.tablex`, `pl.class`, `pl.List`, `pl.Map`
-- @classmod pl.OrderedMap
local tablex = require 'pl.tablex'
local utils = require 'pl.utils'
local List = require 'pl.List'
local index_by,tsort,concat = tablex.index_by,table.sort,table.concat
local class = require 'pl.class'
local Map = require 'pl.Map'
local OrderedMap = class(Map)
OrderedMap._name = 'OrderedMap'
local rawset = rawset
--- construct an OrderedMap.
-- Will throw an error if the argument is bad.
-- @param t optional initialization table, same as for @{OrderedMap:update}
function OrderedMap:_init (t)
rawset(self,'_keys',List())
if t then
local map,err = self:update(t)
if not map then error(err,2) end
end
end
local assert_arg,raise = utils.assert_arg,utils.raise
--- update an OrderedMap using a table.
-- If the table is itself an OrderedMap, then its entries will be appended.
-- if it s a table of the form `{{key1=val1},{key2=val2},...}` these will be appended.
--
-- Otherwise, it is assumed to be a map-like table, and order of extra entries is arbitrary.
-- @tab t a table.
-- @return the map, or nil in case of error
-- @return the error message
function OrderedMap:update (t)
assert_arg(1,t,'table')
if OrderedMap:class_of(t) then
for k,v in t:iter() do
self:set(k,v)
end
elseif #t > 0 then -- an array must contain {key=val} tables
if type(t[1]) == 'table' then
for _,pair in ipairs(t) do
local key,value = next(pair)
if not key then return raise 'empty pair initialization table' end
self:set(key,value)
end
else
return raise 'cannot use an array to initialize an OrderedMap'
end
else
for k,v in pairs(t) do
self:set(k,v)
end
end
return self
end
--- set the key's value. This key will be appended at the end of the map.
--
-- If the value is nil, then the key is removed.
-- @param key the key
-- @param val the value
-- @return the map
function OrderedMap:set (key,val)
if rawget(self, key) == nil and val ~= nil then -- new key
self._keys:append(key) -- we keep in order
rawset(self,key,val) -- don't want to provoke __newindex!
else -- existing key-value pair
if val == nil then
self._keys:remove_value(key)
rawset(self,key,nil)
else
self[key] = val
end
end
return self
end
OrderedMap.__newindex = OrderedMap.set
--- insert a key/value pair before a given position.
-- Note: if the map already contains the key, then this effectively
-- moves the item to the new position by first removing at the old position.
-- Has no effect if the key does not exist and val is nil
-- @int pos a position starting at 1
-- @param key the key
-- @param val the value; if nil use the old value
function OrderedMap:insert (pos,key,val)
local oldval = self[key]
val = val or oldval
if oldval then
self._keys:remove_value(key)
end
if val then
self._keys:insert(pos,key)
rawset(self,key,val)
end
return self
end
--- return the keys in order.
-- (Not a copy!)
-- @return List
function OrderedMap:keys ()
return self._keys
end
--- return the values in order.
-- this is relatively expensive.
-- @return List
function OrderedMap:values ()
return List(index_by(self,self._keys))
end
--- sort the keys.
-- @func cmp a comparison function as for @{table.sort}
-- @return the map
function OrderedMap:sort (cmp)
tsort(self._keys,cmp)
return self
end
--- iterate over key-value pairs in order.
function OrderedMap:iter ()
local i = 0
local keys = self._keys
local idx
return function()
i = i + 1
if i > #keys then return nil end
idx = keys[i]
return idx,self[idx]
end
end
--- iterate over an ordered map (5.2).
-- @within metamethods
-- @function OrderedMap:__pairs
OrderedMap.__pairs = OrderedMap.iter
--- string representation of an ordered map.
-- @within metamethods
function OrderedMap:__tostring ()
local res = {}
for i,v in ipairs(self._keys) do
local val = self[v]
local vs = tostring(val)
if type(val) ~= 'number' then
vs = '"'..vs..'"'
end
res[i] = tostring(v)..'='..vs
end
return '{'..concat(res,',')..'}'
end
return OrderedMap

View file

@ -1,222 +0,0 @@
--- A Set class.
--
-- > Set = require 'pl.Set'
-- > = Set{'one','two'} == Set{'two','one'}
-- true
-- > fruit = Set{'apple','banana','orange'}
-- > = fruit['banana']
-- true
-- > = fruit['hazelnut']
-- nil
-- > colours = Set{'red','orange','green','blue'}
-- > = fruit,colours
-- [apple,orange,banana] [blue,green,orange,red]
-- > = fruit+colours
-- [blue,green,apple,red,orange,banana]
-- [orange]
-- > more_fruits = fruit + 'apricot'
-- > = fruit*colours
-- > = more_fruits, fruit
-- [banana,apricot,apple,orange] [banana,apple,orange]
--
-- Dependencies: `pl.utils`, `pl.tablex`, `pl.class`, `pl.Map`, (`pl.List` if __tostring is used)
-- @module pl.Set
local tablex = require 'pl.tablex'
local utils = require 'pl.utils'
local array_tostring, concat = utils.array_tostring, table.concat
local merge,difference = tablex.merge,tablex.difference
local Map = require 'pl.Map'
local class = require 'pl.class'
local stdmt = utils.stdmt
local Set = stdmt.Set
-- the Set class --------------------
class(Map,nil,Set)
-- note: Set has _no_ methods!
Set.__index = nil
local function makeset (t)
return setmetatable(t,Set)
end
--- create a set. <br>
-- @param t may be a Set, Map or list-like table.
-- @class function
-- @name Set
function Set:_init (t)
t = t or {}
local mt = getmetatable(t)
if mt == Set or mt == Map then
for k in pairs(t) do self[k] = true end
else
for _,v in ipairs(t) do self[v] = true end
end
end
--- string representation of a set.
-- @within metamethods
function Set:__tostring ()
return '['..concat(array_tostring(Set.values(self)),',')..']'
end
--- get a list of the values in a set.
-- @param self a Set
-- @function Set.values
-- @return a list
Set.values = Map.keys
--- map a function over the values of a set.
-- @param self a Set
-- @param fn a function
-- @param ... extra arguments to pass to the function.
-- @return a new set
function Set.map (self,fn,...)
fn = utils.function_arg(1,fn)
local res = {}
for k in pairs(self) do
res[fn(k,...)] = true
end
return makeset(res)
end
--- union of two sets (also +).
-- @param self a Set
-- @param set another set
-- @return a new set
function Set.union (self,set)
return merge(self,set,true)
end
--- modifies '+' operator to allow addition of non-Set elements
--- Preserves +/- semantics - does not modify first argument.
local function setadd(self,other)
local mt = getmetatable(other)
if mt == Set or mt == Map then
return Set.union(self,other)
else
local new = Set(self)
new[other] = true
return new
end
end
--- union of sets.
-- @within metamethods
-- @function Set.__add
Set.__add = setadd
--- intersection of two sets (also *).
-- @param self a Set
-- @param set another set
-- @return a new set
-- @usage
-- > s = Set{10,20,30}
-- > t = Set{20,30,40}
-- > = t
-- [20,30,40]
-- > = Set.intersection(s,t)
-- [30,20]
-- > = s*t
-- [30,20]
function Set.intersection (self,set)
return merge(self,set,false)
end
--- intersection of sets.
-- @within metamethods
-- @function Set.__mul
Set.__mul = Set.intersection
--- new set with elements in the set that are not in the other (also -).
-- @param self a Set
-- @param set another set
-- @return a new set
function Set.difference (self,set)
return difference(self,set,false)
end
--- modifies "-" operator to remove non-Set values from set.
--- Preserves +/- semantics - does not modify first argument.
local function setminus (self,other)
local mt = getmetatable(other)
if mt == Set or mt == Map then
return Set.difference(self,other)
else
local new = Set(self)
new[other] = nil
return new
end
end
--- difference of sets.
-- @within metamethods
-- @function Set.__sub
Set.__sub = setminus
-- a new set with elements in _either_ the set _or_ other but not both (also ^).
-- @param self a Set
-- @param set another set
-- @return a new set
function Set.symmetric_difference (self,set)
return difference(self,set,true)
end
--- symmetric difference of sets.
-- @within metamethods
-- @function Set.__pow
Set.__pow = Set.symmetric_difference
--- is the first set a subset of the second (also <)?.
-- @param self a Set
-- @param set another set
-- @return true or false
function Set.issubset (self,set)
for k in pairs(self) do
if not set[k] then return false end
end
return true
end
--- first set subset of second?
-- @within metamethods
-- @function Set.__lt
Set.__lt = Set.issubset
--- is the set empty?.
-- @param self a Set
-- @return true or false
function Set.isempty (self)
return next(self) == nil
end
--- are the sets disjoint? (no elements in common).
-- Uses naive definition, i.e. that intersection is empty
-- @param s1 a Set
-- @param s2 another set
-- @return true or false
function Set.isdisjoint (s1,s2)
return Set.isempty(Set.intersection(s1,s2))
end
--- size of this set (also # for 5.2).
-- @param s a Set
-- @return size
-- @function Set.len
Set.len = tablex.size
--- cardinality of set (5.2).
-- @within metamethods
-- @function Set.__len
Set.__len = Set.len
--- equality between sets.
-- @within metamethods
function Set.__eq (s1,s2)
return Set.issubset(s1,s2) and Set.issubset(s2,s1)
end
return Set

View file

@ -1,159 +0,0 @@
--- Application support functions.
-- See @{01-introduction.md.Application_Support|the Guide}
--
-- Dependencies: `pl.utils`, `pl.path`
-- @module pl.app
local io,package,require = _G.io, _G.package, _G.require
local utils = require 'pl.utils'
local path = require 'pl.path'
local app = {}
local function check_script_name ()
if _G.arg == nil then error('no command line args available\nWas this run from a main script?') end
return _G.arg[0]
end
--- add the current script's path to the Lua module path.
-- Applies to both the source and the binary module paths. It makes it easy for
-- the main file of a multi-file program to access its modules in the same directory.
-- `base` allows these modules to be put in a specified subdirectory, to allow for
-- cleaner deployment and resolve potential conflicts between a script name and its
-- library directory.
-- @string base optional base directory.
-- @treturn string the current script's path with a trailing slash
function app.require_here (base)
local p = path.dirname(check_script_name())
if not path.isabs(p) then
p = path.join(path.currentdir(),p)
end
if p:sub(-1,-1) ~= path.sep then
p = p..path.sep
end
if base then
p = p..base..path.sep
end
local so_ext = path.is_windows and 'dll' or 'so'
local lsep = package.path:find '^;' and '' or ';'
local csep = package.cpath:find '^;' and '' or ';'
package.path = ('%s?.lua;%s?%sinit.lua%s%s'):format(p,p,path.sep,lsep,package.path)
package.cpath = ('%s?.%s%s%s'):format(p,so_ext,csep,package.cpath)
return p
end
--- return a suitable path for files private to this application.
-- These will look like '~/.SNAME/file', with '~' as with expanduser and
-- SNAME is the name of the script without .lua extension.
-- @string file a filename (w/out path)
-- @return a full pathname, or nil
-- @return 'cannot create' error
function app.appfile (file)
local sname = path.basename(check_script_name())
local name = path.splitext(sname)
local dir = path.join(path.expanduser('~'),'.'..name)
if not path.isdir(dir) then
local ret = path.mkdir(dir)
if not ret then return utils.raise ('cannot create '..dir) end
end
return path.join(dir,file)
end
--- return string indicating operating system.
-- @return 'Windows','OSX' or whatever uname returns (e.g. 'Linux')
function app.platform()
if path.is_windows then
return 'Windows'
else
local f = io.popen('uname')
local res = f:read()
if res == 'Darwin' then res = 'OSX' end
f:close()
return res
end
end
--- return the full command-line used to invoke this script.
-- Any extra flags occupy slots, so that `lua -lpl` gives us `{[-2]='lua',[-1]='-lpl'}`
-- @return command-line
-- @return name of Lua program used
function app.lua ()
local args = _G.arg or error "not in a main program"
local imin = 0
for i in pairs(args) do
if i < imin then imin = i end
end
local cmd, append = {}, table.insert
for i = imin,-1 do
append(cmd, utils.quote_arg(args[i]))
end
return table.concat(cmd,' '),args[imin]
end
--- parse command-line arguments into flags and parameters.
-- Understands GNU-style command-line flags; short (`-f`) and long (`--flag`).
-- These may be given a value with either '=' or ':' (`-k:2`,`--alpha=3.2`,`-n2`);
-- note that a number value can be given without a space.
-- Multiple short args can be combined like so: ( `-abcd`).
-- @tparam {string} args an array of strings (default is the global `arg`)
-- @tab flags_with_values any flags that take values, e.g. `{out=true}`
-- @return a table of flags (flag=value pairs)
-- @return an array of parameters
-- @raise if args is nil, then the global `args` must be available!
function app.parse_args (args,flags_with_values)
if not args then
args = _G.arg
if not args then error "Not in a main program: 'arg' not found" end
end
flags_with_values = flags_with_values or {}
local _args = {}
local flags = {}
local i = 1
while i <= #args do
local a = args[i]
local v = a:match('^-(.+)')
local is_long
if v then -- we have a flag
if v:find '^-' then
is_long = true
v = v:sub(2)
end
if flags_with_values[v] then
if i == #args or args[i+1]:find '^-' then
return utils.raise ("no value for '"..v.."'")
end
flags[v] = args[i+1]
i = i + 1
else
-- a value can also be indicated with = or :
local var,val = utils.splitv (v,'[=:]')
var = var or v
val = val or true
if not is_long then
if #var > 1 then
if var:find '.%d+' then -- short flag, number value
val = var:sub(2)
var = var:sub(1,1)
else -- multiple short flags
for i = 1,#var do
flags[var:sub(i,i)] = true
end
val = nil -- prevents use of var as a flag below
end
else -- single short flag (can have value, defaults to true)
val = val or true
end
end
if val then
flags[var] = val
end
end
else
_args[#_args+1] = a
end
i = i + 1
end
return flags,_args
end
return app

View file

@ -1,493 +0,0 @@
--- Operations on two-dimensional arrays.
-- See @{02-arrays.md.Operations_on_two_dimensional_tables|The Guide}
--
-- Dependencies: `pl.utils`, `pl.tablex`, `pl.types`
-- @module pl.array2d
local tonumber,assert,tostring,io,ipairs,string,table =
_G.tonumber,_G.assert,_G.tostring,_G.io,_G.ipairs,_G.string,_G.table
local setmetatable,getmetatable = setmetatable,getmetatable
local tablex = require 'pl.tablex'
local utils = require 'pl.utils'
local types = require 'pl.types'
local imap,tmap,reduce,keys,tmap2,tset,index_by = tablex.imap,tablex.map,tablex.reduce,tablex.keys,tablex.map2,tablex.set,tablex.index_by
local remove = table.remove
local splitv,fprintf,assert_arg = utils.splitv,utils.fprintf,utils.assert_arg
local byte = string.byte
local stdout = io.stdout
local min = math.min
local array2d = {}
local function obj (int,out)
local mt = getmetatable(int)
if mt then
setmetatable(out,mt)
end
return out
end
local function makelist (res)
return setmetatable(res, require('pl.List'))
end
local function index (t,k)
return t[k]
end
--- return the row and column size.
-- @array2d t a 2d array
-- @treturn int number of rows
-- @treturn int number of cols
function array2d.size (t)
assert_arg(1,t,'table')
return #t,#t[1]
end
--- extract a column from the 2D array.
-- @array2d a 2d array
-- @param key an index or key
-- @return 1d array
function array2d.column (a,key)
assert_arg(1,a,'table')
return makelist(imap(index,a,key))
end
local column = array2d.column
--- map a function over a 2D array
-- @func f a function of at least one argument
-- @array2d a 2d array
-- @param arg an optional extra argument to be passed to the function.
-- @return 2d array
function array2d.map (f,a,arg)
assert_arg(1,a,'table')
f = utils.function_arg(1,f)
return obj(a,imap(function(row) return imap(f,row,arg) end, a))
end
--- reduce the rows using a function.
-- @func f a binary function
-- @array2d a 2d array
-- @return 1d array
-- @see pl.tablex.reduce
function array2d.reduce_rows (f,a)
assert_arg(1,a,'table')
return tmap(function(row) return reduce(f,row) end, a)
end
--- reduce the columns using a function.
-- @func f a binary function
-- @array2d a 2d array
-- @return 1d array
-- @see pl.tablex.reduce
function array2d.reduce_cols (f,a)
assert_arg(1,a,'table')
return tmap(function(c) return reduce(f,column(a,c)) end, keys(a[1]))
end
--- reduce a 2D array into a scalar, using two operations.
-- @func opc operation to reduce the final result
-- @func opr operation to reduce the rows
-- @param a 2D array
function array2d.reduce2 (opc,opr,a)
assert_arg(3,a,'table')
local tmp = array2d.reduce_rows(opr,a)
return reduce(opc,tmp)
end
--- map a function over two arrays.
-- They can be both or either 2D arrays
-- @func f function of at least two arguments
-- @int ad order of first array (1 or 2)
-- @int bd order of second array (1 or 2)
-- @tab a 1d or 2d array
-- @tab b 1d or 2d array
-- @param arg optional extra argument to pass to function
-- @return 2D array, unless both arrays are 1D
function array2d.map2 (f,ad,bd,a,b,arg)
assert_arg(1,a,'table')
assert_arg(2,b,'table')
f = utils.function_arg(1,f)
if ad == 1 and bd == 2 then
return imap(function(row)
return tmap2(f,a,row,arg)
end, b)
elseif ad == 2 and bd == 1 then
return imap(function(row)
return tmap2(f,row,b,arg)
end, a)
elseif ad == 1 and bd == 1 then
return tmap2(f,a,b)
elseif ad == 2 and bd == 2 then
return tmap2(function(rowa,rowb)
return tmap2(f,rowa,rowb,arg)
end, a,b)
end
end
--- cartesian product of two 1d arrays.
-- @func f a function of 2 arguments
-- @array t1 a 1d table
-- @array t2 a 1d table
-- @return 2d table
-- @usage product('..',{1,2},{'a','b'}) == {{'1a','2a'},{'1b','2b'}}
function array2d.product (f,t1,t2)
f = utils.function_arg(1,f)
assert_arg(2,t1,'table')
assert_arg(3,t2,'table')
local res, map = {}, tablex.map
for i,v in ipairs(t2) do
res[i] = map(f,t1,v)
end
return res
end
--- flatten a 2D array.
-- (this goes over columns first.)
-- @array2d t 2d table
-- @return a 1d table
-- @usage flatten {{1,2},{3,4},{5,6}} == {1,2,3,4,5,6}
function array2d.flatten (t)
local res = {}
local k = 1
for _,a in ipairs(t) do -- for all rows
for i = 1,#a do
res[k] = a[i]
k = k + 1
end
end
return makelist(res)
end
--- reshape a 2D array.
-- @array2d t 2d array
-- @int nrows new number of rows
-- @bool co column-order (Fortran-style) (default false)
-- @return a new 2d array
function array2d.reshape (t,nrows,co)
local nr,nc = array2d.size(t)
local ncols = nr*nc / nrows
local res = {}
local ir,ic = 1,1
for i = 1,nrows do
local row = {}
for j = 1,ncols do
row[j] = t[ir][ic]
if not co then
ic = ic + 1
if ic > nc then
ir = ir + 1
ic = 1
end
else
ir = ir + 1
if ir > nr then
ic = ic + 1
ir = 1
end
end
end
res[i] = row
end
return obj(t,res)
end
--- swap two rows of an array.
-- @array2d t a 2d array
-- @int i1 a row index
-- @int i2 a row index
function array2d.swap_rows (t,i1,i2)
assert_arg(1,t,'table')
t[i1],t[i2] = t[i2],t[i1]
end
--- swap two columns of an array.
-- @array2d t a 2d array
-- @int j1 a column index
-- @int j2 a column index
function array2d.swap_cols (t,j1,j2)
assert_arg(1,t,'table')
for i = 1,#t do
local row = t[i]
row[j1],row[j2] = row[j2],row[j1]
end
end
--- extract the specified rows.
-- @array2d t 2d array
-- @tparam {int} ridx a table of row indices
function array2d.extract_rows (t,ridx)
return obj(t,index_by(t,ridx))
end
--- extract the specified columns.
-- @array2d t 2d array
-- @tparam {int} cidx a table of column indices
function array2d.extract_cols (t,cidx)
assert_arg(1,t,'table')
local res = {}
for i = 1,#t do
res[i] = index_by(t[i],cidx)
end
return obj(t,res)
end
--- remove a row from an array.
-- @function array2d.remove_row
-- @array2d t a 2d array
-- @int i a row index
array2d.remove_row = remove
--- remove a column from an array.
-- @array2d t a 2d array
-- @int j a column index
function array2d.remove_col (t,j)
assert_arg(1,t,'table')
for i = 1,#t do
remove(t[i],j)
end
end
local function _parse (s)
local c,r
if s:sub(1,1) == 'R' then
r,c = s:match 'R(%d+)C(%d+)'
r,c = tonumber(r),tonumber(c)
else
c,r = s:match '(.)(.)'
c = byte(c) - byte 'A' + 1
r = tonumber(r)
end
assert(c ~= nil and r ~= nil,'bad cell specifier: '..s)
return r,c
end
--- parse a spreadsheet range.
-- The range can be specified either as 'A1:B2' or 'R1C1:R2C2';
-- a special case is a single element (e.g 'A1' or 'R1C1')
-- @string s a range.
-- @treturn int start col
-- @treturn int start row
-- @treturn int end col
-- @treturn int end row
function array2d.parse_range (s)
if s:find ':' then
local start,finish = splitv(s,':')
local i1,j1 = _parse(start)
local i2,j2 = _parse(finish)
return i1,j1,i2,j2
else -- single value
local i,j = _parse(s)
return i,j
end
end
--- get a slice of a 2D array using spreadsheet range notation. @see parse_range
-- @array2d t a 2D array
-- @string rstr range expression
-- @return a slice
-- @see array2d.parse_range
-- @see array2d.slice
function array2d.range (t,rstr)
assert_arg(1,t,'table')
local i1,j1,i2,j2 = array2d.parse_range(rstr)
if i2 then
return array2d.slice(t,i1,j1,i2,j2)
else -- single value
return t[i1][j1]
end
end
local function default_range (t,i1,j1,i2,j2)
local nr, nc = array2d.size(t)
i1,j1 = i1 or 1, j1 or 1
i2,j2 = i2 or nr, j2 or nc
if i2 < 0 then i2 = nr + i2 + 1 end
if j2 < 0 then j2 = nc + j2 + 1 end
return i1,j1,i2,j2
end
--- get a slice of a 2D array. Note that if the specified range has
-- a 1D result, the rank of the result will be 1.
-- @array2d t a 2D array
-- @int i1 start row (default 1)
-- @int j1 start col (default 1)
-- @int i2 end row (default N)
-- @int j2 end col (default M)
-- @return an array, 2D in general but 1D in special cases.
function array2d.slice (t,i1,j1,i2,j2)
assert_arg(1,t,'table')
i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
local res = {}
for i = i1,i2 do
local val
local row = t[i]
if j1 == j2 then
val = row[j1]
else
val = {}
for j = j1,j2 do
val[#val+1] = row[j]
end
end
res[#res+1] = val
end
if i1 == i2 then res = res[1] end
return obj(t,res)
end
--- set a specified range of an array to a value.
-- @array2d t a 2D array
-- @param value the value (may be a function)
-- @int i1 start row (default 1)
-- @int j1 start col (default 1)
-- @int i2 end row (default N)
-- @int j2 end col (default M)
-- @see tablex.set
function array2d.set (t,value,i1,j1,i2,j2)
i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
for i = i1,i2 do
tset(t[i],value,j1,j2)
end
end
--- write a 2D array to a file.
-- @array2d t a 2D array
-- @param f a file object (default stdout)
-- @string fmt a format string (default is just to use tostring)
-- @int i1 start row (default 1)
-- @int j1 start col (default 1)
-- @int i2 end row (default N)
-- @int j2 end col (default M)
function array2d.write (t,f,fmt,i1,j1,i2,j2)
assert_arg(1,t,'table')
f = f or stdout
local rowop
if fmt then
rowop = function(row,j) fprintf(f,fmt,row[j]) end
else
rowop = function(row,j) f:write(tostring(row[j]),' ') end
end
local function newline()
f:write '\n'
end
array2d.forall(t,rowop,newline,i1,j1,i2,j2)
end
--- perform an operation for all values in a 2D array.
-- @array2d t 2D array
-- @func row_op function to call on each value
-- @func end_row_op function to call at end of each row
-- @int i1 start row (default 1)
-- @int j1 start col (default 1)
-- @int i2 end row (default N)
-- @int j2 end col (default M)
function array2d.forall (t,row_op,end_row_op,i1,j1,i2,j2)
assert_arg(1,t,'table')
i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
for i = i1,i2 do
local row = t[i]
for j = j1,j2 do
row_op(row,j)
end
if end_row_op then end_row_op(i) end
end
end
---- move a block from the destination to the source.
-- @array2d dest a 2D array
-- @int di start row in dest
-- @int dj start col in dest
-- @array2d src a 2D array
-- @int i1 start row (default 1)
-- @int j1 start col (default 1)
-- @int i2 end row (default N)
-- @int j2 end col (default M)
function array2d.move (dest,di,dj,src,i1,j1,i2,j2)
assert_arg(1,dest,'table')
assert_arg(4,src,'table')
i1,j1,i2,j2 = default_range(src,i1,j1,i2,j2)
local nr,nc = array2d.size(dest)
i2, j2 = min(nr,i2), min(nc,j2)
--i1, j1 = max(1,i1), max(1,j1)
dj = dj - 1
for i = i1,i2 do
local drow, srow = dest[i+di-1], src[i]
for j = j1,j2 do
drow[j+dj] = srow[j]
end
end
end
--- iterate over all elements in a 2D array, with optional indices.
-- @array2d a 2D array
-- @tparam {int} indices with indices (default false)
-- @int i1 start row (default 1)
-- @int j1 start col (default 1)
-- @int i2 end row (default N)
-- @int j2 end col (default M)
-- @return either value or i,j,value depending on indices
function array2d.iter (a,indices,i1,j1,i2,j2)
assert_arg(1,a,'table')
local norowset = not (i2 and j2)
i1,j1,i2,j2 = default_range(a,i1,j1,i2,j2)
local i,j = i1-1,j1-1
local row,nr = nil,0
local onr = j2 - j1 + 1
return function()
j = j + 1
if j > nr then
j = j1
i = i + 1
if i > i2 then return nil end
row = a[i]
nr = norowset and #row or onr
end
if indices then
return i,j,row[j]
else
return row[j]
end
end
end
--- iterate over all columns.
-- @array2d a a 2D array
-- @return each column in turn
function array2d.columns (a)
assert_arg(1,a,'table')
local n = a[1][1]
local i = 0
return function()
i = i + 1
if i > n then return nil end
return column(a,i)
end
end
--- new array of specified dimensions
-- @int rows number of rows
-- @int cols number of cols
-- @param val initial value; if it's a function then use `val(i,j)`
-- @return new 2d array
function array2d.new(rows,cols,val)
local res = {}
local fun = types.is_callable(val)
for i = 1,rows do
local row = {}
if fun then
for j = 1,cols do row[j] = val(i,j) end
else
for j = 1,cols do row[j] = val end
end
res[i] = row
end
return res
end
return array2d

View file

@ -1,261 +0,0 @@
--- Provides a reuseable and convenient framework for creating classes in Lua.
-- Two possible notations:
--
-- B = class(A)
-- class.B(A)
--
-- The latter form creates a named class within the current environment. Note
-- that this implicitly brings in `pl.utils` as a dependency.
--
-- See the Guide for further @{01-introduction.md.Simplifying_Object_Oriented_Programming_in_Lua|discussion}
-- @module pl.class
local error, getmetatable, io, pairs, rawget, rawset, setmetatable, tostring, type =
_G.error, _G.getmetatable, _G.io, _G.pairs, _G.rawget, _G.rawset, _G.setmetatable, _G.tostring, _G.type
local compat
-- this trickery is necessary to prevent the inheritance of 'super' and
-- the resulting recursive call problems.
local function call_ctor (c,obj,...)
-- nice alias for the base class ctor
local base = rawget(c,'_base')
if base then
local parent_ctor = rawget(base,'_init')
while not parent_ctor do
base = rawget(base,'_base')
if not base then break end
parent_ctor = rawget(base,'_init')
end
if parent_ctor then
rawset(obj,'super',function(obj,...)
call_ctor(base,obj,...)
end)
end
end
local res = c._init(obj,...)
rawset(obj,'super',nil)
return res
end
--- initializes an __instance__ upon creation.
-- @function class:_init
-- @param ... parameters passed to the constructor
-- @usage local Cat = class()
-- function Cat:_init(name)
-- --self:super(name) -- call the ancestor initializer if needed
-- self.name = name
-- end
--
-- local pussycat = Cat("pussycat")
-- print(pussycat.name) --> pussycat
--- checks whether an __instance__ is derived from some class.
-- Works the other way around as `class_of`. It has two ways of using;
-- 1) call with a class to check against, 2) call without params.
-- @function instance:is_a
-- @param some_class class to check against, or `nil` to return the class
-- @return `true` if `instance` is derived from `some_class`, or if `some_class == nil` then
-- it returns the class table of the instance
-- @usage local pussycat = Lion() -- assuming Lion derives from Cat
-- if pussycat:is_a(Cat) then
-- -- it's true, it is a Lion, but also a Cat
-- end
--
-- if pussycat:is_a() == Lion then
-- -- It's true
-- end
local function is_a(self,klass)
if klass == nil then
-- no class provided, so return the class this instance is derived from
return getmetatable(self)
end
local m = getmetatable(self)
if not m then return false end --*can't be an object!
while m do
if m == klass then return true end
m = rawget(m,'_base')
end
return false
end
--- checks whether an __instance__ is derived from some class.
-- Works the other way around as `is_a`.
-- @function some_class:class_of
-- @param some_instance instance to check against
-- @return `true` if `some_instance` is derived from `some_class`
-- @usage local pussycat = Lion() -- assuming Lion derives from Cat
-- if Cat:class_of(pussycat) then
-- -- it's true
-- end
local function class_of(klass,obj)
if type(klass) ~= 'table' or not rawget(klass,'is_a') then return false end
return klass.is_a(obj,klass)
end
--- cast an object to another class.
-- It is not clever (or safe!) so use carefully.
-- @param some_instance the object to be changed
-- @function some_class:cast
local function cast (klass, obj)
return setmetatable(obj,klass)
end
local function _class_tostring (obj)
local mt = obj._class
local name = rawget(mt,'_name')
setmetatable(obj,nil)
local str = tostring(obj)
setmetatable(obj,mt)
if name then str = name ..str:gsub('table','') end
return str
end
local function tupdate(td,ts,dont_override)
for k,v in pairs(ts) do
if not dont_override or td[k] == nil then
td[k] = v
end
end
end
local function _class(base,c_arg,c)
-- the class `c` will be the metatable for all its objects,
-- and they will look up their methods in it.
local mt = {} -- a metatable for the class to support __call and _handler
-- can define class by passing it a plain table of methods
local plain = type(base) == 'table' and not getmetatable(base)
if plain then
c = base
base = c._base
else
c = c or {}
end
if type(base) == 'table' then
-- our new class is a shallow copy of the base class!
-- but be careful not to wipe out any methods we have been given at this point!
tupdate(c,base,plain)
c._base = base
-- inherit the 'not found' handler, if present
if rawget(c,'_handler') then mt.__index = c._handler end
elseif base ~= nil then
error("must derive from a table type",3)
end
c.__index = c
setmetatable(c,mt)
if not plain then
c._init = nil
end
if base and rawget(base,'_class_init') then
base._class_init(c,c_arg)
end
-- expose a ctor which can be called by <classname>(<args>)
mt.__call = function(class_tbl,...)
local obj
if rawget(c,'_create') then obj = c._create(...) end
if not obj then obj = {} end
setmetatable(obj,c)
if rawget(c,'_init') then -- explicit constructor
local res = call_ctor(c,obj,...)
if res then -- _if_ a ctor returns a value, it becomes the object...
obj = res
setmetatable(obj,c)
end
elseif base and rawget(base,'_init') then -- default constructor
-- make sure that any stuff from the base class is initialized!
call_ctor(base,obj,...)
end
if base and rawget(base,'_post_init') then
base._post_init(obj)
end
return obj
end
-- Call Class.catch to set a handler for methods/properties not found in the class!
c.catch = function(self, handler)
if type(self) == "function" then
-- called using . instead of :
handler = self
end
c._handler = handler
mt.__index = handler
end
c.is_a = is_a
c.class_of = class_of
c.cast = cast
c._class = c
if not rawget(c,'__tostring') then
c.__tostring = _class_tostring
end
return c
end
--- create a new class, derived from a given base class.
-- Supporting two class creation syntaxes:
-- either `Name = class(base)` or `class.Name(base)`.
-- The first form returns the class directly and does not set its `_name`.
-- The second form creates a variable `Name` in the current environment set
-- to the class, and also sets `_name`.
-- @function class
-- @param base optional base class
-- @param c_arg optional parameter to class constructor
-- @param c optional table to be used as class
local class
class = setmetatable({},{
__call = function(fun,...)
return _class(...)
end,
__index = function(tbl,key)
if key == 'class' then
io.stderr:write('require("pl.class").class is deprecated. Use require("pl.class")\n')
return class
end
compat = compat or require 'pl.compat'
local env = compat.getfenv(2)
return function(...)
local c = _class(...)
c._name = key
rawset(env,key,c)
return c
end
end
})
class.properties = class()
function class.properties._class_init(klass)
klass.__index = function(t,key)
-- normal class lookup!
local v = klass[key]
if v then return v end
-- is it a getter?
v = rawget(klass,'get_'..key)
if v then
return v(t)
end
-- is it a field?
return rawget(t,'_'..key)
end
klass.__newindex = function (t,key,value)
-- if there's a setter, use that, otherwise directly set table
local p = 'set_'..key
local setter = klass[p]
if setter then
setter(t,value)
else
rawset(t,key,value)
end
end
end
return class

View file

@ -1,165 +0,0 @@
----------------
--- Lua 5.1/5.2/5.3 compatibility.
-- Ensures that `table.pack` and `package.searchpath` are available
-- for Lua 5.1 and LuaJIT.
-- The exported function `load` is Lua 5.2 compatible.
-- `compat.setfenv` and `compat.getfenv` are available for Lua 5.2, although
-- they are not always guaranteed to work.
-- @module pl.compat
local compat = {}
compat.lua51 = _VERSION == 'Lua 5.1'
local isJit = (tostring(assert):match('builtin') ~= nil)
if isJit then
-- 'goto' is a keyword when 52 compatibility is enabled in LuaJit
compat.jit52 = not loadstring("local goto = 1")
end
compat.dir_separator = _G.package.config:sub(1,1)
compat.is_windows = compat.dir_separator == '\\'
--- execute a shell command.
-- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2
-- @param cmd a shell command
-- @return true if successful
-- @return actual return code
function compat.execute (cmd)
local res1,_,res3 = os.execute(cmd)
if compat.lua51 and not compat.jit52 then
if compat.is_windows then
res1 = res1 > 255 and res1 % 256 or res1
return res1==0,res1
else
res1 = res1 > 255 and res1 / 256 or res1
return res1==0,res1
end
else
if compat.is_windows then
res3 = res3 > 255 and res3 % 256 or res3
return res3==0,res3
else
return not not res1,res3
end
end
end
----------------
-- Load Lua code as a text or binary chunk.
-- @param ld code string or loader
-- @param[opt] source name of chunk for errors
-- @param[opt] mode 'b', 't' or 'bt'
-- @param[opt] env environment to load the chunk in
-- @function compat.load
---------------
-- Get environment of a function.
-- With Lua 5.2, may return nil for a function with no global references!
-- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html)
-- @param f a function or a call stack reference
-- @function compat.getfenv
---------------
-- Set environment of a function
-- @param f a function or a call stack reference
-- @param env a table that becomes the new environment of `f`
-- @function compat.setfenv
if compat.lua51 then -- define Lua 5.2 style load()
if not isJit then -- but LuaJIT's load _is_ compatible
local lua51_load = load
function compat.load(str,src,mode,env)
local chunk,err
if type(str) == 'string' then
if str:byte(1) == 27 and not (mode or 'bt'):find 'b' then
return nil,"attempt to load a binary chunk"
end
chunk,err = loadstring(str,src)
else
chunk,err = lua51_load(str,src)
end
if chunk and env then setfenv(chunk,env) end
return chunk,err
end
else
compat.load = load
end
compat.setfenv, compat.getfenv = setfenv, getfenv
else
compat.load = load
-- setfenv/getfenv replacements for Lua 5.2
-- by Sergey Rozhenko
-- http://lua-users.org/lists/lua-l/2010-06/msg00313.html
-- Roberto Ierusalimschy notes that it is possible for getfenv to return nil
-- in the case of a function with no globals:
-- http://lua-users.org/lists/lua-l/2010-06/msg00315.html
function compat.setfenv(f, t)
f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
local name
local up = 0
repeat
up = up + 1
name = debug.getupvalue(f, up)
until name == '_ENV' or name == nil
if name then
debug.upvaluejoin(f, up, function() return name end, 1) -- use unique upvalue
debug.setupvalue(f, up, t)
end
if f ~= 0 then return f end
end
function compat.getfenv(f)
local f = f or 0
f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
local name, val
local up = 0
repeat
up = up + 1
name, val = debug.getupvalue(f, up)
until name == '_ENV' or name == nil
return val
end
end
--- Lua 5.2 Functions Available for 5.1
-- @section lua52
--- pack an argument list into a table.
-- @param ... any arguments
-- @return a table with field n set to the length
-- @function table.pack
if not table.pack then
function table.pack (...) -- luacheck: ignore
return {n=select('#',...); ...}
end
end
--- unpack a table and return the elements.
-- @param t table to unpack
-- @param[opt] i index from which to start unpacking, defaults to 1
-- @param[opt] t index of the last element to unpack, defaults to #t
-- @return multiple returns values from the table
if not table.unpack then
table.unpack = unpack -- luacheck: ignore
end
------
-- return the full path where a Lua module name would be matched.
-- @param mod module name, possibly dotted
-- @param path a path in the same form as package.path or package.cpath
-- @see path.package_path
-- @function package.searchpath
if not package.searchpath then
local sep = package.config:sub(1,1)
function package.searchpath (mod,path) -- luacheck: ignore
mod = mod:gsub('%.',sep)
for m in path:gmatch('[^;]+') do
local nm = m:gsub('?',mod)
local f = io.open(nm,'r')
if f then f:close(); return nm end
end
end
end
return compat

Some files were not shown because too many files have changed in this diff Show more