UNPKG

ass-compiler

Version:

Parses and compiles ASS subtitle format to easy-to-use data structure.

138 lines (126 loc) 3.92 kB
import { stringifyInfo, stringifyTime, stringifyEffect } from './stringifier.js'; import { stylesFormat, eventsFormat } from './utils.js'; export function decompileStyle({ style, tag }) { const obj = Object.assign({}, style, { PrimaryColour: `&H${tag.a1}${tag.c1}`, SecondaryColour: `&H${tag.a2}${tag.c2}`, OutlineColour: `&H${tag.a3}${tag.c3}`, BackColour: `&H${tag.a4}${tag.c4}`, }); return `Style: ${stylesFormat.map((fmt) => obj[fmt]).join()}`; } const drawingInstructionMap = { M: 'm', L: 'l', C: 'b', }; export function decompileDrawing({ instructions }) { return instructions.map(({ type, points }) => ( [drawingInstructionMap[type]] .concat(...points.map(({ x, y }) => [x, y])) .join(' ') )).join(' '); } const ca = (x) => (n) => (_) => `${n}${x}&H${_}&`; const c = ca('c'); const a = ca('a'); const tagDecompiler = { c1: c(1), c2: c(2), c3: c(3), c4: c(4), a1: a(1), a2: a(2), a3: a(3), a4: a(4), pos: (_) => `pos(${[_.x, _.y]})`, org: (_) => `org(${[_.x, _.y]})`, move: (_) => `move(${[_.x1, _.y1, _.x2, _.y2, _.t1, _.t2]})`, fade: (_) => ( _.type === 'fad' ? `fad(${[_.t1, _.t2]})` : `fade(${[_.a1, _.a2, _.a3, _.t1, _.t2, _.t3, _.t4]})` ), clip: (_) => `${_.inverse ? 'i' : ''}clip(${ _.dots ? `${[_.dots.x1, _.dots.y1, _.dots.x2, _.dots.y2]}` : `${_.scale === 1 ? '' : `${_.scale},`}${decompileDrawing(_.drawing)}` })`, // eslint-disable-next-line no-use-before-define t: (arr) => arr.map((_) => `t(${[_.t1, _.t2, _.accel, decompileTag(_.tag)]})`).join('\\'), }; export function decompileTag(tag) { return Object.keys(tag).map((key) => { const fn = tagDecompiler[key] || ((_) => `${key}${_}`); return `\\${fn(tag[key])}`; }).join(''); } export function decompileSlice(slice) { return slice.fragments.map(({ tag, text, drawing }) => { const tagText = decompileTag(tag); return `${tagText ? `{${tagText}}` : ''}${drawing ? decompileDrawing(drawing) : text}`; }).join(''); } export function decompileText(dia, style) { return dia.slices .filter((slice) => slice.fragments.length) .map((slice, idx) => { const sliceCopy = JSON.parse(JSON.stringify(slice)); const tag = {}; if (idx) { tag.r = slice.style === dia.style ? '' : slice.style; } else { if (style.Alignment !== dia.alignment) { tag.an = dia.alignment; } ['pos', 'org', 'move', 'fade', 'clip'].forEach((key) => { if (dia[key]) { tag[key] = dia[key]; } }); } // make sure additional tags are first sliceCopy.fragments[0].tag = Object.assign(tag, sliceCopy.fragments[0].tag); return sliceCopy; }) .map(decompileSlice) .join(''); } function getMargin(margin, styleMargin) { return margin === styleMargin ? '0000' : margin; } export function decompileDialogue(dia, style) { return `Dialogue: ${[ dia.layer, stringifyTime(dia.start), stringifyTime(dia.end), dia.style, dia.name, getMargin(dia.margin.left, style.MarginL), getMargin(dia.margin.right, style.MarginR), getMargin(dia.margin.vertical, style.MarginV), stringifyEffect(dia.effect), decompileText(dia, style), ].join()}`; } export function decompile({ info, width, height, collisions, styles, dialogues }) { return [ '[Script Info]', stringifyInfo(Object.assign({}, info, { PlayResX: width, PlayResY: height, Collisions: collisions, })), '', '[V4+ Styles]', `Format: ${stylesFormat.join(', ')}`, ...Object.keys(styles).map((name) => decompileStyle(styles[name])), '', '[Events]', `Format: ${eventsFormat.join(', ')}`, ...dialogues .sort((x, y) => x.start - y.start || x.end - y.end) .map((dia) => decompileDialogue(dia, styles[dia.style].style)), '', ].join('\n'); }