Aegisub/OverLua/docs/sample2.lua
Niels Martin Hansen 25842be356 Another, much more complex and interesting, sample.
Originally committed to SVN as r1545.
2007-08-29 22:28:20 +00:00

235 lines
7.3 KiB
Lua

--[[
Sample script for OverLua
- demonstrate basic reading in an ASS subtitle file and rendering its lines on the video
Given into the public domain.
(You can do anything you want with this file, with no restrictions whatsoever.
You don't get any warranties of any kind either, though.)
Originally authored by Niels Martin Hansen.
]]
-- Set up some parameters
timing_input_file = overlua_datastring
-- Just the font name to use.
font_name = "Arial"
-- This is height in pixels, I suppose ;)
font_size = 40
-- This is the position of the _baseline_ of the text, neither top, bottom nor center!
ypos = 50
-- Duration of fadein/out in seconds
fadetime = 1
-- Error out if no file name was given (data= in Avisynth invocation)
assert(timing_input_file, "Missing timing input file for sample effect.")
-- ASS file reading stuff
function parsenum(str)
return tonumber(str) or 0
end
function parse_ass_time(ass)
local h, m, s, cs = ass:match("(%d+):(%d+):(%d+)%.(%d+)")
return parsenum(cs)/100 + parsenum(s) + parsenum(m)*60 + parsenum(h)*3600
end
function parse_k_timing(text)
local syls = {}
local cleantext = ""
local i = 1
for timing, syltext in text:gmatch("{\\k(%d+)}([^{]*)") do
local syl = {dur = parsenum(timing)/100, text = syltext, i = i}
table.insert(syls, syl)
cleantext = cleantext .. syltext
i = i + 1
end
return syls, cleantext
end
function read_input_file(name)
for line in io.lines(name) do
local start_time, end_time, fx, text = line:match("Dialogue: 0,(.-),(.-),Default,,0000,0000,0000,(.-),(.*)")
if text then
local ls = {}
ls.start_time = parse_ass_time(start_time)
ls.end_time = parse_ass_time(end_time)
ls.fx = fx
ls.rawtext = text
ls.kara, ls.cleantext = parse_k_timing(text)
table.insert(lines, ls)
end
end
end
function init()
if inited then return end
inited = true
lines = {}
read_input_file(timing_input_file)
end
-- Get/create a "sparks" texture, singleton-style
function get_sparks_texture(width, height)
-- Check if it already exists, just return it then
if sparks_texture then return sparks_texture end
-- We'll make a 128x128 black image with some blurred whitish spots on
local surf = cairo.image_surface_create(128, 128, "rgb24")
local c = surf.create_context()
-- Paint it all black
c.set_source_rgb(0,0,0)
c.paint()
-- Then create a very light yellow
c.set_source_rgb(1,1,0.9)
-- And create 50 small, random circles
for i = 1, 50 do
local x, y = math.random(120)+4, math.random(120)+4
c.arc(x, y, 3, 0, 2*math.pi)
c.fill()
end
-- And blur the result
raster.gaussian_blur(surf, 2.5)
-- Then create a texture of it.
-- sparks_texture becomes a global variable
sparks_texture = cairo.pattern_create_for_surface(surf)
sparks_texture.set_extend("repeat")
return sparks_texture
end
function render_frame(f, t)
init()
-- Create a blurred copy of the video frame
local fsurf = f.create_cairo_surface()
raster.gaussian_blur(fsurf, 5)
-- Function to create "wobble" effect on the text
local function blubble_mapper(x, y)
local nx = x + math.sin(x/30 + y/10 + t*2.0)*3
local ny = y + math.cos(y/20 + x/20 + t*2.3)*3
return nx, ny
end
-- Find lines to be drawn
for i, line in pairs(lines) do
-- Check if the line is within time range.
-- "In time range" means starts fadetime seconds later than current time or ends "fadetime" seconds earlier.
if line.start_time <= t+fadetime and line.end_time > t-fadetime then
local x = 0
local y = ypos
-- Prepare a surface to draw on
local surf = cairo.image_surface_create(f.width, 200, "argb32")
local c = surf.create_context()
-- Select the font
c.select_font_face(font_name)
c.set_font_size(font_size)
-- Get the text extents for the line text if we don't have them already
if not line.te then line.te = c.text_extents(line.cleantext); line.fe = c.font_extents() end
-- And calculate the start X to have the line centered
x = (f.width - line.te.width) / 2 - line.te.x_bearing
-- Then make a path for the text
c.move_to(x, y)
c.text_path(line.cleantext)
-- Create the "wobble" effect on the text
-- First get a Path object for the text
local path = c.copy_path()
-- Run the path through the mapping function
path.map_coords(blubble_mapper)
-- Clear the path in the context
c.new_path()
-- And add the modified path back
c.append_path(path)
-- Prepare drawing the text outline
c.set_line_width(8)
-- Red outline
c.set_source_rgba(1, 0, 0, 1)
-- Stroke it but keep the path in the canvas
c.stroke_preserve()
-- Blur this outline
raster.gaussian_blur(surf, 1.5)
-- Prepare another, smaller outline on top
c.set_line_width(3)
-- This one is white
c.set_source_rgba(1, 1, 1, 1)
-- Stroke that one too, but clear the path afterwards
c.stroke()
-- Now loop over the syllables to draw them one by one
local sumdur = line.start_time
for j, syl in pairs(line.kara) do
-- Get the text extents for this syllable
if not syl.te then syl.te = c.text_extents(syl.text) end
-- Prepare the path
c.move_to(x, y)
c.text_path(syl.text)
-- Wobble the path; this will work because the mapper is deterministic on X, Y and timestamp
local path = c.copy_path()
path.map_coords(blubble_mapper)
c.new_path()
c.append_path(path)
-- And advance X position
x = x + syl.te.x_advance
-- Now figure out whether this syllable is the active one or not
-- Use a more complicated test, this makes the first syllable be highlighted
-- also while the line is fading in, and the last while the line is fading out.
if (syl.i == 1 and t < sumdur+syl.dur) or
(syl.i == #line.kara and t > sumdur) or
(t >= sumdur and t < sumdur+syl.dur) then
-- Get the "sparks" texture
local sparks = get_sparks_texture()
-- Prepare a transformation matrix for it
local texmat = cairo.matrix_create()
texmat.init_rotate(t/10)
texmat.scale(3, 3)
sparks.set_matrix(texmat)
-- Use the texture
c.set_source(sparks)
-- And fill the path
c.fill()
else
-- Not the active syllable, fill it with a blurred video frame
-- Remember fsurf is the blurred video frame
c.set_source_surface(fsurf, 0, 0)
c.fill_preserve()
-- Also add a slight darkening to the fill
c.set_source_rgba(0, 0, 0, 0.2)
c.fill()
end
-- Advance the sum of syllable durations
sumdur = sumdur + syl.dur
end
-- Figure out whether we're past the actual start/end time of the line and do some fading then
local final = surf
if t < line.start_time or t > line.end_time then
-- Make invisibility the amount the line is invisible
local invisibility
if t < line.start_time then
invisibility = (line.start_time - t) / fadetime
else
invisibility = (t - line.end_time) / fadetime
end
-- We'll need a new surface object here
final = cairo.image_surface_create(surf.get_width(), surf.get_height(), "argb32")
local c = final.create_context()
-- So we can alpha-blend the original drawn text image onto it using invisibility as alpha
c.set_source_surface(surf, 0, 0)
c.paint_with_alpha(1-invisibility)
-- And then do some heavy blur-out
raster.gaussian_blur(final, invisibility*15)
end
-- Finally just overlay the text image on the video
f.overlay_cairo_surface(final, 0, 0)
end
end
end