paroles
Version:
A library for parsing, making, modifying and playing LRC format lyrics
288 lines (287 loc) • 8.71 kB
JavaScript
var C = Object.defineProperty;
var I = (r, e, t) => e in r ? C(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
var h = (r, e, t) => (I(r, typeof e != "symbol" ? e + "" : e, t), t);
const g = `
`, A = `\r
`;
function S(r) {
const e = Array.from(r.matchAll(/\n/g)).length;
return e === 0 ? g : Array.from(r.matchAll(/\r\n/g)).length === e ? A : g;
}
const y = (r, e) => {
const [t, i] = r.toString().split(".");
return t.length < e ? new Array(e - t.length).fill("0").join("") + t + (i ? "." + i : "") : t + (i ? "." + i : "");
}, $ = /^\[[^:]+:[^\]]*\]/, x = /\[(\d+):(\d+)(?:.(\d+))?\]/g, p = /\[([a-z]+):([^\]]*)\]/, E = {
resolveConflict: "merge"
}, j = {
eol: g,
resolveConflict: "merge"
};
class a {
constructor(e, t) {
h(this, "_offset");
h(this, "_option");
h(this, "eol");
h(this, "info");
h(this, "lines");
if (this._option = {
...E,
...t || {}
}, typeof e == "string") {
this.eol = S(e);
const i = a.parse(e, {
eol: this.eol,
resolveConflict: this._option.resolveConflict
});
this.info = i.info, this.lines = i.lines, this._offset = i.info.offset || 0;
} else if (typeof e > "u")
this.eol = g, this.info = {}, this.lines = [], this._offset = 0;
else {
const i = e.clone();
this.eol = i.eol, this.info = i.info, this.lines = i.lines, this._offset = i.info.offset || 0, this._option = i._option;
}
}
clone() {
return new a(this.toString());
}
toString() {
return a.stringify(this);
}
at(e) {
return this.lines.at(e);
}
getIndexByTime(e) {
const t = this.lines.findIndex((i) => i.time - this._offset > e);
return t <= 0 ? t : t - 1;
}
atTime(e) {
const t = this.getIndexByTime(e);
return this.lines.at(t);
}
setOffset(e) {
this._offset = e;
}
merge(e, t) {
const i = new a(e);
t != null && t.resolveInfo ? this.info = t.resolveInfo(this.info, i.info) : t != null && t.override ? this.info = {
...this.info,
...i.info
} : this.info = {
...i.info,
...this.info
};
for (const s of i.lines) {
let n = this.getIndexByTime(s.time);
if (n = n === -1 ? this.lines.length - 1 : n, this.lines[n].time === s.time)
if (t != null && t.resolveConflict)
this.lines[n] = {
time: s.time,
text: t.resolveConflict(this.lines[n].text, s.text)
};
else if (t != null && t.override)
this.lines[n] = {
time: s.time,
text: s.text
};
else
continue;
else
n === this.lines.length - 1 ? this.lines.push(s) : n === 0 && s.time < this.lines[n].time ? this.lines.unshift(s) : this.lines.splice(n + 1, 0, s);
}
return this;
}
insert(e) {
const t = Array.isArray(e) ? e : [e];
for (const i of t) {
let s = this.getIndexByTime(i.time);
s = s === -1 ? this.lines.length - 1 : s, this.lines[s].time === i.time ? this.lines[s] = i : s === this.lines.length - 1 ? this.lines.push(i) : s === 0 && i.time < this.lines[s].time ? this.lines.unshift(i) : this.lines.splice(s + 1, 0, i);
}
return this;
}
remove(e) {
if (typeof e == "string")
this.lines = this.lines.filter((t) => t.text !== e);
else if (e instanceof RegExp)
this.lines = this.lines.filter((t) => !e.test(t.text));
else {
let t = this.getIndexByTime(e.time);
t = t === -1 ? this.lines.length - 1 : t, this.lines[t].time === e.time && this.lines[t].text === e.text ? this.lines.splice(t, 1) : console.error("lyrics line not existed");
}
return this;
}
replace(e, t) {
if ((typeof e == "string" || e instanceof RegExp) && typeof t == "string") {
const i = this.lines.filter((s) => typeof e == "string" ? s.text === e : e.test(s.text));
i.length && i.forEach((s) => {
typeof e == "string" ? s.text = t : s.text = s.text.replace(e, t);
});
} else if (d(e) && d(t)) {
const i = [];
this.lines.forEach((s, n) => {
s.text === e.text && s.time === e.time && i.push(n);
}), i.forEach((s) => {
this.lines[s] = t;
});
}
return this;
}
setInfo(e) {
return this.info = {
...this.info,
...e
}, this;
}
static stringify(e) {
const t = [];
for (const [n, f] of Object.entries(e.info)) {
const c = B(n);
t.push(`[${c}:${f}]`);
}
const i = {};
for (const n of e.lines)
i[n.text] ? i[n.text].push(n.time) : i[n.text] = [n.time];
const s = Object.entries(i).map(([n, f]) => ({
text: n,
time: f
})).sort((n, f) => n.time[0] - f.time[0]).map((n) => `${n.time.map((c) => `[${O(c)}]`).join("")}${n.text}`);
return t.push(...s), t.join(e.eol);
}
static parse(e, t) {
const i = {
...j,
...t || {}
}, s = e.split(i.eol).map((c) => c.trim()).filter((c) => $.test(c)), n = {}, f = [];
for (const c of s)
if (x.test(c)) {
const o = c.replace(x, "").trim();
Array.from(c.matchAll(x)).forEach((u) => {
const m = +u[1], v = +u[2], b = (u[3] ? +`0.${u[3]}` : 0).toFixed(2), T = (m * 60 + v).toString(), _ = b.slice(1);
f.push({
time: Number(T + _),
text: o
});
});
} else if (p.test(c)) {
const o = c.match(p) || ["", ""], l = z(o[1].trim()), u = o[2].trim();
if (l)
if (l !== "offset")
n[l] = u || "";
else {
const m = +u;
n[l] = isNaN(m) ? 0 : +(m / 1e3).toFixed(2);
}
}
return f.sort(function(c, o) {
return c.time - o.time;
}), f.forEach((c, o, l) => {
!l[o + 1] || c.time !== l[o + 1].time || (i.resolveConflict === "merge" ? l[o].text = `${l[o].text}${i.eol}${l[o + 1].text}` : i.resolveConflict === "overwrite" ? l[o].text = l[o + 1].text : typeof i.resolveConflict == "function" && (l[o].text = i.resolveConflict(l[o].text, l[o + 1].text)), l.splice(o + 1, 1));
}), {
info: n,
lines: f
};
}
}
function z(r) {
switch (r) {
case "al":
return "album";
case "ar":
return "artist";
case "au":
return "author";
case "ti":
return "title";
case "by":
return "creator";
case "offset":
return "offset";
case "length":
return "length";
case "re":
return "editor";
case "ve":
return "version";
default:
return;
}
}
function B(r) {
switch (r) {
case "album":
return "al";
case "artist":
return "ar";
case "author":
return "au";
case "title":
return "ti";
case "creator":
return "by";
case "offset":
return "offset";
case "length":
return "length";
case "editor":
return "re";
case "version":
return "ve";
default:
return "";
}
}
function O(r) {
const e = y(Math.floor(r / 60), 2), t = y((r % 60).toFixed(2), 2);
return `${e}:${t}`;
}
function d(r) {
return !!r && typeof r == "object" && "text" in r && "time" in r;
}
class F {
constructor(e) {
h(this, "lyrics");
h(this, "currentTime");
h(this, "_currentLine");
h(this, "_subscriptions");
this.lyrics = e, this.currentTime = 0, this._subscriptions = {
linechange: [],
lyricschange: []
};
}
updateTime(e) {
this.currentTime = e, this._currentLine !== this.getCurrentLine() && (this._currentLine = this.getCurrentLine(), this._subscriptions.linechange.forEach((t) => {
const i = this.getCurrentIndex(), s = this.lyrics.lines.at(i);
t(
(s == null ? void 0 : s.text) || "",
i === -1 ? this.lyrics.lines.length - 1 : i
);
}));
}
getCurrentLine() {
var e;
return ((e = this.lyrics.atTime(this.currentTime)) == null ? void 0 : e.text) || "";
}
getCurrentIndex() {
return this.lyrics.getIndexByTime(this.currentTime);
}
on(...[e, t]) {
e === "linechange" ? this._subscriptions.linechange.push(t) : e === "lyricschange" && this._subscriptions.lyricschange.push(t);
}
off(e, t) {
if (!e) {
this._subscriptions = {
linechange: [],
lyricschange: []
};
return;
}
const i = this._subscriptions[e].findIndex((s) => s === t);
i > -1 ? this._subscriptions.linechange.splice(i, 1) : t ? console.error("LyricsPlayer.off(): handler not correct.") : this._subscriptions.linechange = [];
}
rewind(e) {
this.currentTime = 0, this._currentLine = void 0, e && (this.lyrics = e, this._subscriptions.lyricschange.forEach((t) => t()));
}
}
export {
a as Lyrics,
F as LyricsPlayer
};