Aegisub/aegisub/automation/include/karaskel-base.auto3

439 lines
15 KiB
Plaintext

--[[
Copyright (c) 2005, Niels Martin Hansen
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Aegisub Group nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
]]
-- Aegisub Automation include file
-- This file is meant as a support file for the various karaoke skeleton
-- scripts, to avoid including unneeded code
include("utils.auto3")
-- karaskel
-- This is a gloabl table defining various parameters, controlling what the
-- skeleton will do. Not all parameters are defined in this base.
karaskel = {
-- Does this script need positioning information?
engage_positioning = false,
-- Syllable precalc parameters
precalc_start_progress = 0, -- progress precalc starts at
precalc_end_progress = 50, -- and where it ends at
-- Does this script use furigana information? (has no effect without positioning)
engage_furigana = false,
-- Furigana parameters
furigana_scale = 0.4, -- relative size of furigana
-- Default effect-name for inline effects (read manual)
inline_fx_default = "regular",
-- Set this to the name of the style used for out-of-line effect specifications, if any (read manual on this!)
ool_fx_style = false,
-- Show tracing messages?
engage_trace = false
}
function karaskel.warning(s)
aegisub.output_debug("WARNING! " .. s)
end
function karaskel.trace(s)
if karaskel.engage_trace then
aegisub.output_debug(s)
else
-- A little optimisation
karaskel.trace = function() end
end
end
-- parse_syllable_data
-- Generates the line.karaoke table
-- This was moved to Lua code from the C++ code for various technical reasons
function karaskel.parse_syllable_data(meta, styles, lines)
for i = 0, lines.n-1 do
local l = lines[i]
local ltext = l.text
local indrawing = false
l.karaoke = { n = 0 }
local cursyl = {
duration = 0,
kind = "",
text = "",
text_stripped = ""
}
l.text_stripped = ""
local function report(misc)
karaskel.trace("l.text_stripped = " .. l.text_stripped)
karaskel.trace(string.format("cursyl: dur=%d kind=%s text='%s' text_stripped='%s'", cursyl.duration, cursyl.kind, cursyl.text, cursyl.text_stripped))
karaskel.trace(misc)
end
report("starting parsing")
while ltext ~= "" do
-- Find text part up until next tag start
local tagstart = string.find(ltext, "{")
local textpart
if not tagstart then
-- No tag start was found, rest of line is text
textpart = ltext
ltext = ""
else
-- Tag start was found, cut text part out
textpart = string.sub(ltext, 1, tagstart-1)
ltext = string.sub(ltext, tagstart+1) -- skip over opening brace
end
-- Append text to relevant strings
if not indrawing then
l.text_stripped = l.text_stripped .. textpart
cursyl.text_stripped = cursyl.text_stripped .. textpart
end
cursyl.text = cursyl.text .. textpart
report(string.format("tagstart=%d, textpart='%s', ltext='%s'", tagstart or -1, textpart, ltext))
-- If we're out of line text, we're done
if ltext == "" then
break
end
-- Now find the tag group end
local tagend = string.find(ltext, "}")
local tagpart
if not tagend then
-- Technically wrong, unclosed tag group
tagpart = ltext
ltext = ""
else
-- Take tag group and rest of text
tagpart = string.sub(ltext, 1, tagend-1)
ltext = string.sub(ltext, tagend+1)
end
karaskel.trace(string.format("tagend=%d, tagpart='%s', ltext='%s'", tagend or -1, tagpart, ltext))
-- Look for interesting tags (karaoke and drawing)
while tagpart ~= "" do
local tagstart, tagend, tag, param = string.find(tagpart, "\\([kKp]%a*)(%d+)")
karaskel.trace(string.format("tagstart=%d, tagend=%d, tag=%s, param=%s", tagstart or -1, tagend or -1, tag or "", param or ""))
param = param and tonumber(param) or 0
if tag and string.find(tag, "^[kK]") then
-- Karaoke tag, split stuff up
-- If the kara tag wasn't the first thing in the group, prepend previous stuff
if tagstart > 1 then
cursyl.text = cursyl.text .. "{" .. string.sub(tagpart, 1, tagstart) .. "}"
end
-- Store last tag to table and prepare new one
l.karaoke[l.karaoke.n] = cursyl
l.karaoke.n = l.karaoke.n + 1
cursyl = {
duration = tonumber(param),
kind = tag,
text = "",
text_stripped = ""
}
-- Remove up to and including this tag from the tagpart
tagpart = string.sub(tagpart, tagend+1)
elseif tag and tag == "p" then
-- Switch drawing-mode on/off
indrawing = param > 0
else
-- No more interesting tags here
if tagpart ~= "" then
cursyl.text = cursyl.text .. "{" .. tagpart .. "}"
end
break
end
report(string.format("indrawing=%s", indrawing and "t" or "f"))
end
end
l.karaoke[l.karaoke.n] = cursyl
l.karaoke.n = l.karaoke.n + 1
end
end
-- precalc_syllable_data
-- Adds various extra fields to the line and syllable tables
-- See the implementation and/or Aegisub help file for more information
function karaskel.precalc_syllable_data(meta, styles, lines)
karaskel.trace("precalc_syllable_data")
aegisub.set_status("Preparing syllable-data")
-- Fix missing resolution data
if meta.res_x == 0 and meta_res_y == 0 then
meta.res_x = 384
meta.res_y = 288
elseif meta.res_x == 0 then
-- This is braindead, but it's how TextSub does things...
if meta.res_y == 1024 then
meta.res_x = 1280
else
meta.res_x = meta.res_y / 3 * 4
end
elseif meta.res_y == 0 then
-- As if 1280x960 didn't exist
if meta.res_x == 1280 then
meta.res_y = 1024
else
meta.res_y = meta.res_x * 3 / 4
end
end
for i = 0, lines.n-1 do
karaskel.trace("precalc_syllable_data:2:"..i)
aegisub.report_progress(karaskel.precalc_start_progress + i/lines.n*(karaskel.precalc_end_progress-karaskel.precalc_start_progress))
local line, style = lines[i]
-- Index number of the line
line.i = i
-- Linked list-style access
line.prev = lines[i-1]
line.next = lines[i+1]
karaskel.trace("precalc_syllable_data:3:")
if line.kind == "dialogue" or line.kind == "comment" then
karaskel.trace("precalc_syllable_data:4:")
local style = styles[line.style]
if not style then
-- ok, so the named style does not exist... well there MUST be at least ONE style (assume this)
-- pick the first one
style = styles[0]
karaskel.warning(string.format("You have a line using a style named \"%s\", but that style does not exist! Using the first defined style (\"%s\") instead.", line.style, style.name))
end
if karaskel.engage_furigana then
karaskel.split_furigana_data(line)
line.text_stripped = ""
for k = 0, line.karaoke.n-1 do
line.text_stripped = line.text_stripped .. line.karaoke[k].text_stripped
end
end
-- This should make things more predictable
line.text_stripped = trim(line.text_stripped)
if karaskel.engage_positioning then
-- Line dimensions
line.width, line.height, line.ascent, line.extlead = aegisub.text_extents(style, line.text_stripped)
karaskel.trace(string.format("precalc_syllable_data:5: text_stripped='%s', width=%d", line.text_stripped, line.width))
-- Line position
line.centerleft = math.floor((meta.res_x - line.width) / 2)
line.centerright = meta.res_x - line.centerleft
end
-- Line duration, in miliseconds
line.duration = (line.end_time - line.start_time) * 10
-- Style reference
line.styleref = style
karaskel.trace("precalc_syllable_data:6:")
-- Process the syllables
local curtime = 0
local sumtext = ""
local inline_fx = karaskel.inline_fx_default
for j = 0, line.karaoke.n-1 do
karaskel.trace("precalc_syllable_data:7::"..j)
local syl = line.karaoke[j]
-- Syllable index
syl.i = j
-- Check for inline-effect
karaskel.trace("testing for inline_fx in: " .. syl.text)
local a, b, new_inline_fx = string.find(syl.text, "{%\\?%-(.*)}")
if new_inline_fx then
karaskel.trace("caught new inline_fx: " .. new_inline_fx)
inline_fx = new_inline_fx
end
syl.inline_fx = inline_fx
-- Do positioning calculations, if applicable
sumtext = sumtext .. syl.text_stripped
karaskel.trace("new sumtext = " .. sumtext)
if karaskel.engage_positioning then
-- Summed text dimensions
local sumwidth = aegisub.text_extents(style, sumtext)
karaskel.trace("sumwidth = " .. sumwidth)
-- Strip some spaces
local tmp1, tmp2, prespc, syltxt, postspc = string.find(syl.text_stripped, "^([ \t]*)(.-)([ \t]*)$")
-- Pre/post space dimensions
local prespc_width = aegisub.text_extents(style, prespc)
local postspc_width = aegisub.text_extents(style, postspc)
karaskel.trace("space capture lengths = " .. string.len(prespc) .. ", " .. string.len(syltxt) .. ", " .. string.len(postspc))
karaskel.trace("space widths = " .. prespc_width .. ", " .. postspc_width)
-- Syllable dimensions
syl.width, syl.height, syl.ascent, syl.extlead = aegisub.text_extents(style, syltxt)
karaskel.trace("syllable text, width = " .. syltxt .. ", " .. syl.width)
karaskel.trace("precalc_syllable_data:8::")
-- Syllable positioning
syl.right = sumwidth - postspc_width
syl.left = sumwidth - syl.width + prespc_width
syl.center = math.floor(syl.left + (syl.right - syl.left) / 2)
karaskel.trace("syllable left, center, right = " .. syl.left .. ", " .. syl.center .. ", " .. syl.right)
if syl.furigana then
karaskel.calc_furigana_sizes(line, syl)
end
end
-- Start and end times in miliseconds
syl.start_time = curtime
syl.end_time = curtime + syl.duration*10
curtime = syl.end_time
end
end
end
end
-- Rebuild the entire karaoke data list, splitting out and adding furigana data
-- This also does joining of syllables with "#" as text
-- (Joining of furigana syllables with # as text seems useless for now, so it's not done.)
function karaskel.split_furigana_data(line)
if line.kind ~= "dialogue" then
karaskel.trace("skipping line, not dialogue")
return
end
karaskel.trace("split_furigana_data:1")
line.furigana = {n=0}
local newkara = {n=0}
local curtime = 0
for i = 0, line.karaoke.n-1 do
karaskel.trace("split_furigana_data:2:"..i)
local syl = line.karaoke[i]
local a, b, maintext, furitext = string.find(syl.text_stripped, "^(.*)|(.*)$")
if not maintext then
maintext = syl.text_stripped
end
karaskel.trace("split_furigana_data:3:"..i..":"..maintext)
local highlight = { duration = syl.duration, start_time = curtime*10, end_time = curtime*10+syl.duration*10 }
curtime = curtime + syl.duration
if maintext == "#" then
karaskel.trace("split_furigana_data:4:"..i..":a")
-- repeat character
newkara[newkara.n-1].duration = newkara[newkara.n-1].duration + syl.duration
syl = newkara[newkara.n-1]
else
karaskel.trace("split_furigana_data:4:"..i..":b")
syl.furigana = {n=0, text=""} -- essentially a list of syllables in the syllable :o
syl.highlights = {n=0}
syl.text_stripped = maintext
newkara[newkara.n] = syl
newkara.n = newkara.n + 1
end
karaskel.trace("split_furigana_data:5:"..i)
syl.highlights[syl.highlights.n] = highlight
syl.highlights.n = syl.highlights.n + 1
if a then
karaskel.trace("split_furigana_data:6:"..i)
local furi = { text = furitext, duration = highlight.duration, start_time = highlight.start_time, end_time = highlight.end_time }
syl.furigana[syl.furigana.n] = furi
syl.furigana.n = syl.furigana.n + 1
syl.furigana.text = syl.furigana.text .. furitext
end
end
line.karaoke = newkara
end
function karaskel.calc_furigana_sizes(line, syl)
-- assume the sizes for the main syllable itself has been calculated already
local ls = line.styleref
local style = { -- only copy what's needed for text_extents
fontname = ls.fontname,
fontsize = ls.fontsize * karaskel.furigana_scale,
bold = ls.bold,
italic = ls.italic,
underline = ls.underline,
strikeout = ls.strikeout,
scale_x = ls.scale_x,
scale_y = ls.scale_y,
spacing = ls.spacing,
encoding = ls.encoding
}
syl.furigana.width, syl.furigana.height = aegisub.text_extents(style, syl.furigana.text)
syl.furigana.scale = syl.width / syl.furigana.width * 100
syl.furigana.fontsize = style.fontsize
if syl.furigana.scale > 100 then
syl.furigana.scale = 100
else
syl.furigana.width = syl.width
end
style.scale_x = style.scale_x * syl.furigana.scale / 100
local left = syl.left + (syl.width - syl.furigana.width)/2
for i = 0, syl.furigana.n-1 do
local f = syl.furigana[i]
f.width, f.height = aegisub.text_extents(style, f.text)
f.left = left
f.center = f.left + f.width/2
f.right = f.left + f.width
left = f.right
end
end
-- Everything else is done in the process_lines function
function karaskel.process_lines(meta, styles, lines, config)
karaskel.trace("new skeleton")
karaskel.trace("skel_process_lines")
-- Do a little pre-calculation for each line and syllable
karaskel.parse_syllable_data(meta, styles, lines)
karaskel.precalc_syllable_data(meta, styles, lines)
karaskel.trace("skel_process_lines:2")
-- A var for the new output
local result = {n=0}
local ool_fx = {}
aegisub.set_status("Running main-processing")
-- Now do the usual processing
for i = 0, lines.n-1 do
karaskel.trace("skel_process_lines:3:"..i)
aegisub.report_progress(50+i/lines.n*50)
if (lines[i].kind == "dialogue" or lines[i].kind == "comment") and lines[i].style == karaskel.ool_fx_style then
karaskel.trace("skel_process_lines: parsing ool fx: " .. lines[i].text)
local fx = {}
fx.head, fx.tail = string.headtail(lines[i].text)
karaskel.trace("--- head: '" .. fx.head .. "'")
karaskel.trace("--- tail: '" .. fx.tail .. "'")
fx.line = lines[i]
fx.start_time, fx.end_time = fx.line.start_time, fx.line.end_time
ool_fx[fx.head] = fx
else
-- Get replacement lines
lines[i].ool_fx = ool_fx
repl = do_line(meta, styles, config, lines[i])
karaskel.trace("skel_process_lines:4:"..i..":"..repl.n)
-- Append to result table
for j = 1, repl.n do
result.n = result.n + 1
result[result.n] = repl[j]
end
end
end
-- Done, return the stuff
return result
end
process_lines = karaskel.process_lines