subsrt-ts
Version:
Subtitle JavaScript library and command line tool with no dependencies.
134 lines (133 loc) • 4.53 kB
JavaScript
import { buildHandler } from "../handler.js";
const FORMAT_NAME = "lrc";
const helper = {
/**
* Converts a time string in format of mm:ss.ff or mm:ss,ff to milliseconds.
* @param s The time string to convert
* @throws {TypeError} If the time string is invalid
* @returns Milliseconds
*/
toMilliseconds: (s) => {
const match = /^\s*(\d+):(\d{1,2})(?:[.,](\d{1,3}))?\s*$/.exec(s);
if (!match) {
throw new TypeError(`Invalid time format: ${s}`);
}
const mm = parseInt(match[1], 10);
const ss = parseInt(match[2], 10);
const ff = match[3] ? parseInt(match[3], 10) : 0;
const ms = mm * 60 * 1000 + ss * 1000 + ff * 10;
return ms;
},
/**
* Converts milliseconds to a time string in format of mm:ss.ff.
* @param ms Milliseconds
* @returns Time string in format of mm:ss.ff
*/
toTimeString: (ms) => {
const mm = Math.floor(ms / 1000 / 60);
const ss = Math.floor((ms / 1000) % 60);
const ff = Math.floor(ms % 1000);
const time = `${(mm < 10 ? "0" : "") + mm}:${ss < 10 ? "0" : ""}${ss}.${ff < 100 ? "0" : ""}${ff < 10 ? "0" : Math.floor(ff / 10)}`;
return time;
},
};
/**
* Parses captions in LRC format.
* @param content The subtitle content
* @param options Parse options
* @returns Parsed captions
* @see https://en.wikipedia.org/wiki/LRC_%28file_format%29
*/
const parse = (content, options) => {
let prev = null;
const captions = [];
// const eol = options.eol || "\r\n";
const parts = content.split(/\r?\n/);
for (const part of parts) {
if (!part || part.trim().length === 0) {
continue;
}
// LRC content
const regex = /^\[(\d{1,2}:\d{1,2}(?:[.,]\d{1,3})?)\](.*)(?:\r?\n)*$/;
const match = regex.exec(part);
if (match) {
const caption = {};
caption.type = "caption";
caption.start = helper.toMilliseconds(match[1]);
caption.end = caption.start + 2000;
caption.duration = caption.end - caption.start;
caption.content = match[2];
caption.text = caption.content;
captions.push(caption);
// Update previous
if (prev) {
prev.end = caption.start;
prev.duration = prev.end - prev.start;
}
prev = caption;
continue;
}
// LRC meta
const meta = /^\[(\w+):([^\]]*)\](?:\r?\n)*$/.exec(part);
if (meta) {
const caption = {};
caption.type = "meta";
caption.tag = meta[1];
if (meta[2]) {
caption.data = meta[2];
}
captions.push(caption);
continue;
}
if (options.verbose) {
console.warn("Unknown part", part);
}
}
return captions;
};
/**
* Builds captions in LRC format.
* @param captions The captions to build
* @param options Build options
* @returns The built captions string in LRC format
* @see https://en.wikipedia.org/wiki/LRC_%28file_format%29
*/
const build = (captions, options) => {
var _a;
let content = "";
let lyrics = false;
const eol = (_a = options.eol) !== null && _a !== void 0 ? _a : "\r\n";
for (const caption of captions) {
if (caption.type === "meta") {
if (caption.tag && caption.data && typeof caption.data === "string") {
content += `[${caption.tag}:${caption.data.replace(/[\r\n]+/g, " ")}]${eol}`;
}
continue;
}
if (!caption.type || caption.type === "caption") {
if (!lyrics) {
content += eol; //New line when lyrics start
lyrics = true;
}
content += `[${helper.toTimeString(caption.start)}]${caption.text}${eol}`;
continue;
}
if (options.verbose) {
console.log("SKIP:", caption);
}
}
return content;
};
/**
* Detects whether the content is in LRC format.
* @param content The subtitle content
* @returns Format name if detected, or null if not detected
*/
const detect = (content) => {
/*
[04:48.28]Sister, perfume?
*/
return /\r?\n\[\d+:\d{1,2}(?:[.,]\d{1,3})?\].*\r?\n/.test(content);
};
export default buildHandler({ name: FORMAT_NAME, build, detect, helper, parse });
export { FORMAT_NAME as name, build, detect, helper, parse };