mirror of https://github.com/odrling/Aegisub
439 lines
15 KiB
Plaintext
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
|