--[[ 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