webvtt2ass
Version:
Convert WebVTT into ASS Subtitle
101 lines (91 loc) • 3.74 kB
JavaScript
;
const he = require("he");
const TimeSpan = require("./timespan");
const default_font = "Arial";
function assHeader(font) {
font = font || default_font;
const header = [
"[Script Info]\n; Script generated by webvtt2ass\nScriptType: v4.00+\nWrapStyle: 0\nScaledBorderAndShadow: yes\nYCbCr Matrix: None\nPlayResX: 1920\nPlayResY: 1080\n",
"[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding",
`Style: Default,${font},50,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0.5,2,10,10,10,1`,
`Style: Ruby,${font},50,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0.5,2,10,10,10,1`,
"[Events]\nFormat: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text",
"Comment: 0,00:00:00.00,00:00:00.00,Ruby,,0,0,0,template furi,{\\pos(!line.left+syl.center!,!line.middle-line.height!)\\an5}",
"Comment: 0,00:00:00.00,00:00:00.00,Ruby,,0,0,0,template syl,{\\pos(!line.left+syl.center!,!line.middle!)\\an5}"
];
return header.join("\n") + "\n";
}
function html2assColor(color) {
if (color.startsWith("color")) {
return `&H${color.substr(9, 2)}${color.substr(7, 2)}${color.substr(5, 2)}&`;
}
// todo: cast red to &H0000FF&
return color;
}
//src: <ruby>明日<rt>あした</tr></ruby>また<ruby>会<rt>あう</rt></ruby><ruby>時<rt>とき</rt></ruby>
//tgt: {\k}明日|あした{\k}また{\k}会|あう{\k}時|とき
//ref: http://docs.aegisub.org/manual/Furigana_karaoke
function parseTreeRuby(tree) {
let ret = "{\\k}";
tree.children.forEach((item) => {
if (item.type === "text") {
ret += item.value;
} else if (item.type === "object") {
if (item.name === "rt") {
ret += "|" + parseTreeInner(item, true).text;
ret += "{\\k}";
}
}
});
return ret;
}
function parseTreeInner(tree, noTag) {
const nodes = tree.children;
let ret = {text: ""};
if (tree.name === "ruby") {
ret.text += parseTreeRuby(tree);
ret.style = "Ruby";
} else {
if (tree.name === "v") {
ret.voice = tree.value;
}
nodes.forEach((item) => {
if (item.type === "object") {
const temp = parseTreeInner(item);
ret.text += temp.text;
ret.style = temp.style;
if (ret.voice === undefined) {
ret.voice = temp.voice;
}
} else {
ret.text += item.value;
}
});
if (tree.name !== undefined && noTag === undefined) {
if (tree.name !== "v") {
// handle class
if (tree.name === "c") {
const classes = tree.classes
// TODO: handle style info
} else {
ret.text = "{\\" + tree.name + "1}" + ret.text + "{\\" + tree.name + "0}";
}
}
}
}
return ret;
}
function parseSegment(line) {
let ret = parseTreeInner(line.tree);
if (ret.voice === undefined) {
ret.voice = "";
}
if (ret.style === undefined) {
ret.style = "Default";
}
ret.text = he.decode(ret.text).replace(/\n/g, "\\N");
ret.begin = new TimeSpan(line.startTime).toString();
ret.end = new TimeSpan(line.endTime).toString();
return ret;
}
module.exports = {parseSegment: parseSegment, assHeader: assHeader};