[aegisub] more macros

This commit is contained in:
odrling 2020-10-05 05:46:57 +02:00
parent be1a5af335
commit 4c481ffc38
7 changed files with 6943 additions and 0 deletions

View file

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

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

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

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff