@uimaxbai/am-lyrics 1.1.1 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +1 -0
- package/dist/src/AmLyrics.d.ts +3 -0
- package/dist/src/AmLyrics.d.ts.map +1 -1
- package/dist/src/am-lyrics.js +383 -46
- package/dist/src/am-lyrics.js.map +1 -1
- package/dist/src/react.js +383 -46
- package/dist/src/react.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/AmLyrics.ts +445 -50
- package/test.cjs +13 -0
- package/test.js +13 -0
- package/ttml_test.js +149 -0
package/bun.lock
CHANGED
package/dist/src/AmLyrics.d.ts
CHANGED
|
@@ -84,6 +84,9 @@ export declare class AmLyrics extends LitElement {
|
|
|
84
84
|
* Uses search endpoint, prefers synced lyrics.
|
|
85
85
|
*/
|
|
86
86
|
private static fetchLyricsFromLrclib;
|
|
87
|
+
private static fetchLyricsFromGenius;
|
|
88
|
+
private static calculateLineAlignments;
|
|
89
|
+
private static parseTTML;
|
|
87
90
|
private static convertKPoeLyrics;
|
|
88
91
|
private static toMilliseconds;
|
|
89
92
|
firstUpdated(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AmLyrics.d.ts","sourceRoot":"","sources":["../../src/AmLyrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAO,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"AmLyrics.d.ts","sourceRoot":"","sources":["../../src/AmLyrics.ts"],"names":[],"mappings":"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"}
|
package/dist/src/am-lyrics.js
CHANGED
|
@@ -309,7 +309,7 @@ class GoogleService {
|
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
const VERSION = '1.1.
|
|
312
|
+
const VERSION = '1.1.4';
|
|
313
313
|
const INSTRUMENTAL_THRESHOLD_MS = 7000; // Show dots for gaps >= 7s
|
|
314
314
|
const KPOE_SERVERS = [
|
|
315
315
|
'https://lyricsplus.binimum.org',
|
|
@@ -332,6 +332,7 @@ const TIDAL_SERVERS = [
|
|
|
332
332
|
'https://hifi-one.spotisaver.net',
|
|
333
333
|
'https://hifi-two.spotisaver.net',
|
|
334
334
|
];
|
|
335
|
+
const GENIUS_WORKER_URL = 'https://fetch-genius.samidy.workers.dev/';
|
|
335
336
|
class AmLyrics extends i {
|
|
336
337
|
constructor() {
|
|
337
338
|
super(...arguments);
|
|
@@ -505,9 +506,20 @@ class AmLyrics extends i {
|
|
|
505
506
|
});
|
|
506
507
|
}
|
|
507
508
|
}
|
|
509
|
+
if (collectedSources.length === 0 && resolvedMetadata?.metadata) {
|
|
510
|
+
const geniusResult = await AmLyrics.fetchLyricsFromGenius(resolvedMetadata.metadata);
|
|
511
|
+
if (geniusResult && geniusResult.lines.length > 0) {
|
|
512
|
+
collectedSources.push({
|
|
513
|
+
lines: geniusResult.lines,
|
|
514
|
+
source: 'Genius',
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
508
518
|
this.hasFetchedAllProviders =
|
|
509
519
|
collectedSources.length === 0 ||
|
|
510
|
-
collectedSources.some(s => s.source === 'LRCLIB' ||
|
|
520
|
+
collectedSources.some(s => s.source === 'LRCLIB' ||
|
|
521
|
+
s.source === 'Tidal' ||
|
|
522
|
+
s.source === 'Genius');
|
|
511
523
|
if (collectedSources.length > 0) {
|
|
512
524
|
this.availableSources = AmLyrics.mergeAndSortSources(collectedSources);
|
|
513
525
|
this.currentSourceIndex = 0;
|
|
@@ -584,6 +596,8 @@ class AmLyrics extends i {
|
|
|
584
596
|
return 14;
|
|
585
597
|
if (lower.includes('lrclib') && isUnsynced)
|
|
586
598
|
return 15;
|
|
599
|
+
if (lower.includes('genius'))
|
|
600
|
+
return 16;
|
|
587
601
|
return 20;
|
|
588
602
|
}
|
|
589
603
|
static mergeAndSortSources(collectedSources) {
|
|
@@ -627,6 +641,12 @@ class AmLyrics extends i {
|
|
|
627
641
|
newSources.push({ lines: lrclibResult.lines, source: 'LRCLIB' });
|
|
628
642
|
}
|
|
629
643
|
}
|
|
644
|
+
if (!this.availableSources.some(s => s.source.toLowerCase().includes('genius'))) {
|
|
645
|
+
const geniusResult = await AmLyrics.fetchLyricsFromGenius(resolvedMetadata.metadata);
|
|
646
|
+
if (geniusResult && geniusResult.lines.length > 0) {
|
|
647
|
+
newSources.push({ lines: geniusResult.lines, source: 'Genius' });
|
|
648
|
+
}
|
|
649
|
+
}
|
|
630
650
|
if (newSources.length > 0) {
|
|
631
651
|
this.availableSources = AmLyrics.mergeAndSortSources([
|
|
632
652
|
...this.availableSources,
|
|
@@ -831,6 +851,80 @@ class AmLyrics extends i {
|
|
|
831
851
|
return 10;
|
|
832
852
|
};
|
|
833
853
|
const allResults = [];
|
|
854
|
+
// Try cache API first
|
|
855
|
+
try {
|
|
856
|
+
const cacheParams = new URLSearchParams({
|
|
857
|
+
track: title,
|
|
858
|
+
artist,
|
|
859
|
+
});
|
|
860
|
+
if (metadata.album) {
|
|
861
|
+
cacheParams.append('album', metadata.album);
|
|
862
|
+
}
|
|
863
|
+
if (metadata.durationMs && metadata.durationMs > 0) {
|
|
864
|
+
cacheParams.append('duration', Math.round(metadata.durationMs / 1000).toString());
|
|
865
|
+
}
|
|
866
|
+
const cacheUrl = `https://lyrics-api.binimum.org/?${cacheParams.toString()}`;
|
|
867
|
+
const cacheRes = await fetch(cacheUrl);
|
|
868
|
+
if (cacheRes.ok) {
|
|
869
|
+
const cacheData = await cacheRes.json();
|
|
870
|
+
if (cacheData.results && cacheData.results.length > 0) {
|
|
871
|
+
const result = cacheData.results[0];
|
|
872
|
+
if (result.timing_type === 'word' && result.lyricsUrl) {
|
|
873
|
+
const ttmlRes = await fetch(result.lyricsUrl);
|
|
874
|
+
if (ttmlRes.ok) {
|
|
875
|
+
const ttmlText = await ttmlRes.text();
|
|
876
|
+
const lines = AmLyrics.parseTTML(ttmlText);
|
|
877
|
+
if (lines && lines.length > 0) {
|
|
878
|
+
allResults.push({ lines, source: 'BiniLyrics' });
|
|
879
|
+
return allResults;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
// Not word type, try QQ
|
|
885
|
+
const qqParams = new URLSearchParams(params);
|
|
886
|
+
qqParams.set('source', 'qq');
|
|
887
|
+
const qqUrl = `https://lyricsplus.binimum.org/v2/lyrics/get?${qqParams.toString()}`;
|
|
888
|
+
try {
|
|
889
|
+
const qqRes = await fetch(qqUrl);
|
|
890
|
+
if (qqRes.ok) {
|
|
891
|
+
const payload = await qqRes.json();
|
|
892
|
+
const lines = AmLyrics.convertKPoeLyrics(payload);
|
|
893
|
+
const hasWordSync = lines?.some((line) => line.text &&
|
|
894
|
+
Array.isArray(line.text) &&
|
|
895
|
+
line.text.length > 1);
|
|
896
|
+
if (lines && lines.length > 0 && hasWordSync) {
|
|
897
|
+
allResults.push({ lines, source: 'QQ (Cache Fallback)' });
|
|
898
|
+
return allResults;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
catch (qqError) {
|
|
903
|
+
// Ignore QQ fetch error
|
|
904
|
+
}
|
|
905
|
+
// If QQ fails or has no word sync, fall back to bini lyrics
|
|
906
|
+
if (result.lyricsUrl) {
|
|
907
|
+
const ttmlRes = await fetch(result.lyricsUrl);
|
|
908
|
+
if (ttmlRes.ok) {
|
|
909
|
+
const ttmlText = await ttmlRes.text();
|
|
910
|
+
const lines = AmLyrics.parseTTML(ttmlText);
|
|
911
|
+
if (lines && lines.length > 0) {
|
|
912
|
+
allResults.push({
|
|
913
|
+
lines,
|
|
914
|
+
source: 'Apple Music (Cache Fallback)',
|
|
915
|
+
});
|
|
916
|
+
return allResults;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
catch (e) {
|
|
925
|
+
// eslint-disable-next-line no-console
|
|
926
|
+
console.error('Cache API failed', e);
|
|
927
|
+
}
|
|
834
928
|
// Shuffle servers so we pick a random one first, with all others as fallback
|
|
835
929
|
// Limit to 2 servers to prevent unnecessary API spam when Apple lyrics are missing
|
|
836
930
|
const shuffledServers = [...KPOE_SERVERS]
|
|
@@ -910,6 +1004,7 @@ class AmLyrics extends i {
|
|
|
910
1004
|
if (!match) {
|
|
911
1005
|
// Skip non-timestamped lines (headers like [ti:], [ar:], etc.)
|
|
912
1006
|
// eslint-disable-next-line no-continue
|
|
1007
|
+
// eslint-disable-next-line no-continue
|
|
913
1008
|
continue;
|
|
914
1009
|
}
|
|
915
1010
|
const minutes = parseInt(match[1], 10);
|
|
@@ -929,6 +1024,7 @@ class AmLyrics extends i {
|
|
|
929
1024
|
const endtime = i + 1 < parsed.length ? parsed[i + 1].timestamp : timestamp + 5000;
|
|
930
1025
|
// Skip empty lines (instrumental gaps)
|
|
931
1026
|
if (!text.trim()) {
|
|
1027
|
+
// eslint-disable-next-line no-continue
|
|
932
1028
|
// eslint-disable-next-line no-continue
|
|
933
1029
|
continue;
|
|
934
1030
|
}
|
|
@@ -972,6 +1068,7 @@ class AmLyrics extends i {
|
|
|
972
1068
|
// eslint-disable-next-line no-await-in-loop
|
|
973
1069
|
const searchResponse = await fetch(`${normalizedBase}/search/?${searchParams.toString()}`);
|
|
974
1070
|
if (!searchResponse.ok) {
|
|
1071
|
+
// eslint-disable-next-line no-continue
|
|
975
1072
|
// eslint-disable-next-line no-continue
|
|
976
1073
|
continue;
|
|
977
1074
|
}
|
|
@@ -979,6 +1076,7 @@ class AmLyrics extends i {
|
|
|
979
1076
|
const searchData = await searchResponse.json();
|
|
980
1077
|
const items = searchData?.data?.items;
|
|
981
1078
|
if (!Array.isArray(items) || items.length === 0) {
|
|
1079
|
+
// eslint-disable-next-line no-continue
|
|
982
1080
|
// eslint-disable-next-line no-continue
|
|
983
1081
|
continue;
|
|
984
1082
|
}
|
|
@@ -992,6 +1090,7 @@ class AmLyrics extends i {
|
|
|
992
1090
|
}
|
|
993
1091
|
const trackId = bestTrack?.id;
|
|
994
1092
|
if (!trackId) {
|
|
1093
|
+
// eslint-disable-next-line no-continue
|
|
995
1094
|
// eslint-disable-next-line no-continue
|
|
996
1095
|
continue;
|
|
997
1096
|
}
|
|
@@ -999,6 +1098,7 @@ class AmLyrics extends i {
|
|
|
999
1098
|
// eslint-disable-next-line no-await-in-loop
|
|
1000
1099
|
const lyricsResponse = await fetch(`${normalizedBase}/lyrics/?id=${trackId}`);
|
|
1001
1100
|
if (!lyricsResponse.ok) {
|
|
1101
|
+
// eslint-disable-next-line no-continue
|
|
1002
1102
|
// eslint-disable-next-line no-continue
|
|
1003
1103
|
continue;
|
|
1004
1104
|
}
|
|
@@ -1032,7 +1132,7 @@ class AmLyrics extends i {
|
|
|
1032
1132
|
if (!title || !artist)
|
|
1033
1133
|
return null;
|
|
1034
1134
|
try {
|
|
1035
|
-
const searchQuery = `${
|
|
1135
|
+
const searchQuery = `${artist} ${title}`;
|
|
1036
1136
|
const params = new URLSearchParams({ q: searchQuery });
|
|
1037
1137
|
const response = await fetch(`https://lrclib.net/api/search?${params.toString()}`, {
|
|
1038
1138
|
headers: {
|
|
@@ -1085,6 +1185,268 @@ class AmLyrics extends i {
|
|
|
1085
1185
|
}
|
|
1086
1186
|
return null;
|
|
1087
1187
|
}
|
|
1188
|
+
static async fetchLyricsFromGenius(metadata) {
|
|
1189
|
+
const title = metadata.title?.trim();
|
|
1190
|
+
const artist = metadata.artist?.trim();
|
|
1191
|
+
if (!title || !artist)
|
|
1192
|
+
return null;
|
|
1193
|
+
try {
|
|
1194
|
+
const params = new URLSearchParams({ title, artist });
|
|
1195
|
+
const response = await fetch(`${GENIUS_WORKER_URL}?${params.toString()}`);
|
|
1196
|
+
if (!response.ok)
|
|
1197
|
+
return null;
|
|
1198
|
+
const data = await response.json();
|
|
1199
|
+
if (data.lyrics) {
|
|
1200
|
+
const plainLines = data.lyrics
|
|
1201
|
+
.split('\n')
|
|
1202
|
+
.map((l) => l.trim())
|
|
1203
|
+
.filter((l) => l && !l.startsWith('['));
|
|
1204
|
+
if (plainLines.length > 0) {
|
|
1205
|
+
const lines = plainLines.map((text) => ({
|
|
1206
|
+
text: [
|
|
1207
|
+
{
|
|
1208
|
+
text,
|
|
1209
|
+
part: false,
|
|
1210
|
+
timestamp: 0,
|
|
1211
|
+
endtime: 0,
|
|
1212
|
+
},
|
|
1213
|
+
],
|
|
1214
|
+
background: false,
|
|
1215
|
+
backgroundText: [],
|
|
1216
|
+
oppositeTurn: false,
|
|
1217
|
+
timestamp: 0,
|
|
1218
|
+
endtime: 0,
|
|
1219
|
+
isWordSynced: false,
|
|
1220
|
+
}));
|
|
1221
|
+
return { lines, source: 'Genius' };
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
catch {
|
|
1226
|
+
// eslint-disable-next-line no-console
|
|
1227
|
+
console.error('No Genius lyrics found');
|
|
1228
|
+
}
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
static calculateLineAlignments(lineSingers, agentTypes) {
|
|
1232
|
+
const lineSideAssignments = new Array(lineSingers.length).fill(undefined);
|
|
1233
|
+
let currentSideIsLeft = true;
|
|
1234
|
+
let lastPersonSingerId = null;
|
|
1235
|
+
let rightCount = 0;
|
|
1236
|
+
let totalCount = 0;
|
|
1237
|
+
lineSingers.forEach((singerId, index) => {
|
|
1238
|
+
let sideClass;
|
|
1239
|
+
if (singerId) {
|
|
1240
|
+
let type = agentTypes[singerId];
|
|
1241
|
+
if (!type) {
|
|
1242
|
+
if (singerId === 'v1000') {
|
|
1243
|
+
type = 'group';
|
|
1244
|
+
}
|
|
1245
|
+
else if (singerId === 'v2000') {
|
|
1246
|
+
type = 'other';
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
type = 'person';
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (type === 'group') {
|
|
1253
|
+
sideClass = 'start';
|
|
1254
|
+
}
|
|
1255
|
+
else {
|
|
1256
|
+
if (lastPersonSingerId === null) {
|
|
1257
|
+
if (type === 'other') {
|
|
1258
|
+
currentSideIsLeft = false;
|
|
1259
|
+
}
|
|
1260
|
+
else {
|
|
1261
|
+
currentSideIsLeft = true;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
else if (singerId !== lastPersonSingerId) {
|
|
1265
|
+
currentSideIsLeft = !currentSideIsLeft;
|
|
1266
|
+
}
|
|
1267
|
+
sideClass = currentSideIsLeft ? 'start' : 'end';
|
|
1268
|
+
lastPersonSingerId = singerId;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
if (sideClass) {
|
|
1272
|
+
totalCount += 1;
|
|
1273
|
+
if (sideClass === 'end')
|
|
1274
|
+
rightCount += 1;
|
|
1275
|
+
}
|
|
1276
|
+
lineSideAssignments[index] = sideClass;
|
|
1277
|
+
});
|
|
1278
|
+
if (totalCount > 0 && Math.round((rightCount / totalCount) * 100) >= 85) {
|
|
1279
|
+
const flip = (s) => {
|
|
1280
|
+
if (s === 'start')
|
|
1281
|
+
return 'end';
|
|
1282
|
+
if (s === 'end')
|
|
1283
|
+
return 'start';
|
|
1284
|
+
return s;
|
|
1285
|
+
};
|
|
1286
|
+
for (let i = 0; i < lineSideAssignments.length; i += 1) {
|
|
1287
|
+
lineSideAssignments[i] = flip(lineSideAssignments[i]);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return lineSideAssignments;
|
|
1291
|
+
}
|
|
1292
|
+
static parseTTML(ttmlString) {
|
|
1293
|
+
try {
|
|
1294
|
+
const parser = new DOMParser();
|
|
1295
|
+
const doc = parser.parseFromString(ttmlString, 'text/xml');
|
|
1296
|
+
const translations = {};
|
|
1297
|
+
const transliterations = {};
|
|
1298
|
+
const agentMap = {};
|
|
1299
|
+
const agents = doc.getElementsByTagName('ttm:agent');
|
|
1300
|
+
for (let i = 0; i < agents.length; i += 1) {
|
|
1301
|
+
const agent = agents[i];
|
|
1302
|
+
const id = agent.getAttribute('xml:id');
|
|
1303
|
+
const type = agent.getAttribute('type');
|
|
1304
|
+
if (id && type) {
|
|
1305
|
+
agentMap[id] = type;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
const translationNodes = doc.getElementsByTagName('translation');
|
|
1309
|
+
for (let i = 0; i < translationNodes.length; i += 1) {
|
|
1310
|
+
const texts = translationNodes[i].getElementsByTagName('text');
|
|
1311
|
+
for (let j = 0; j < texts.length; j += 1) {
|
|
1312
|
+
const textNode = texts[j];
|
|
1313
|
+
const key = textNode.getAttribute('for');
|
|
1314
|
+
if (key && textNode.textContent) {
|
|
1315
|
+
translations[key] = textNode.textContent;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
const transliterationNodes = doc.getElementsByTagName('transliteration');
|
|
1320
|
+
for (let i = 0; i < transliterationNodes.length; i += 1) {
|
|
1321
|
+
const texts = transliterationNodes[i].getElementsByTagName('text');
|
|
1322
|
+
for (let j = 0; j < texts.length; j += 1) {
|
|
1323
|
+
const textNode = texts[j];
|
|
1324
|
+
const key = textNode.getAttribute('for');
|
|
1325
|
+
if (key && textNode.textContent) {
|
|
1326
|
+
transliterations[key] = textNode.textContent
|
|
1327
|
+
.trim()
|
|
1328
|
+
.replace(/\s+/g, ' ');
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
const timeToMs = (timeStr) => {
|
|
1333
|
+
if (!timeStr)
|
|
1334
|
+
return 0;
|
|
1335
|
+
const parts = timeStr.split(':');
|
|
1336
|
+
let seconds = 0;
|
|
1337
|
+
if (parts.length === 2) {
|
|
1338
|
+
seconds = parseInt(parts[0], 10) * 60 + parseFloat(parts[1]);
|
|
1339
|
+
}
|
|
1340
|
+
else if (parts.length === 3) {
|
|
1341
|
+
seconds =
|
|
1342
|
+
parseInt(parts[0], 10) * 3600 +
|
|
1343
|
+
parseInt(parts[1], 10) * 60 +
|
|
1344
|
+
parseFloat(parts[2]);
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
seconds = parseFloat(parts[0]);
|
|
1348
|
+
}
|
|
1349
|
+
return Math.round(seconds * 1000);
|
|
1350
|
+
};
|
|
1351
|
+
const lines = [];
|
|
1352
|
+
const pNodes = doc.getElementsByTagName('p');
|
|
1353
|
+
const lineSingers = [];
|
|
1354
|
+
for (let i = 0; i < pNodes.length; i += 1) {
|
|
1355
|
+
lineSingers.push(pNodes[i].getAttribute('ttm:agent') || undefined);
|
|
1356
|
+
}
|
|
1357
|
+
const alignments = AmLyrics.calculateLineAlignments(lineSingers, agentMap);
|
|
1358
|
+
for (let i = 0; i < pNodes.length; i += 1) {
|
|
1359
|
+
const p = pNodes[i];
|
|
1360
|
+
const key = p.getAttribute('itunes:key');
|
|
1361
|
+
const beginMs = timeToMs(p.getAttribute('begin'));
|
|
1362
|
+
const endMs = timeToMs(p.getAttribute('end'));
|
|
1363
|
+
let songPart;
|
|
1364
|
+
if (p.parentNode && p.parentNode.tagName === 'div') {
|
|
1365
|
+
songPart =
|
|
1366
|
+
p.parentNode.getAttribute('itunes:songPart') ||
|
|
1367
|
+
undefined;
|
|
1368
|
+
}
|
|
1369
|
+
const mainSyllables = [];
|
|
1370
|
+
const bgSyllables = [];
|
|
1371
|
+
const spans = p.getElementsByTagName('span');
|
|
1372
|
+
if (spans.length > 0) {
|
|
1373
|
+
for (let j = 0; j < spans.length; j += 1) {
|
|
1374
|
+
const span = spans[j];
|
|
1375
|
+
if (span.getAttribute('ttm:role') === 'x-bg') {
|
|
1376
|
+
const bgInnerSpans = span.getElementsByTagName('span');
|
|
1377
|
+
for (let k = 0; k < bgInnerSpans.length; k += 1) {
|
|
1378
|
+
const bgSpan = bgInnerSpans[k];
|
|
1379
|
+
let bgText = bgSpan.textContent || '';
|
|
1380
|
+
const nextNode = bgSpan.nextSibling;
|
|
1381
|
+
if (nextNode &&
|
|
1382
|
+
nextNode.nodeType === 3 &&
|
|
1383
|
+
/^\s/.test(nextNode.textContent || '') &&
|
|
1384
|
+
!bgText.endsWith(' ')) {
|
|
1385
|
+
bgText += ' ';
|
|
1386
|
+
}
|
|
1387
|
+
bgSyllables.push({
|
|
1388
|
+
text: bgText,
|
|
1389
|
+
timestamp: timeToMs(bgSpan.getAttribute('begin')),
|
|
1390
|
+
endtime: timeToMs(bgSpan.getAttribute('end')),
|
|
1391
|
+
part: false,
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
// eslint-disable-next-line no-continue
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
if (span.parentNode &&
|
|
1398
|
+
span.parentNode.getAttribute?.('ttm:role') === 'x-bg') {
|
|
1399
|
+
// eslint-disable-next-line no-continue
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
let text = span.textContent || '';
|
|
1403
|
+
const nextNode = span.nextSibling;
|
|
1404
|
+
if (nextNode &&
|
|
1405
|
+
nextNode.nodeType === 3 &&
|
|
1406
|
+
/^\s/.test(nextNode.textContent || '') &&
|
|
1407
|
+
!text.endsWith(' ')) {
|
|
1408
|
+
text += ' ';
|
|
1409
|
+
}
|
|
1410
|
+
mainSyllables.push({
|
|
1411
|
+
text,
|
|
1412
|
+
timestamp: timeToMs(span.getAttribute('begin')),
|
|
1413
|
+
endtime: timeToMs(span.getAttribute('end')),
|
|
1414
|
+
part: false,
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
mainSyllables.push({
|
|
1420
|
+
text: p.textContent?.trim() || '',
|
|
1421
|
+
timestamp: beginMs,
|
|
1422
|
+
endtime: endMs,
|
|
1423
|
+
part: false,
|
|
1424
|
+
lineSynced: true,
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
const alignment = alignments[i];
|
|
1428
|
+
lines.push({
|
|
1429
|
+
text: mainSyllables,
|
|
1430
|
+
background: bgSyllables.length > 0,
|
|
1431
|
+
backgroundText: bgSyllables,
|
|
1432
|
+
timestamp: beginMs,
|
|
1433
|
+
endtime: endMs,
|
|
1434
|
+
isWordSynced: spans.length > 0,
|
|
1435
|
+
alignment,
|
|
1436
|
+
songPart,
|
|
1437
|
+
translation: key ? translations[key] : undefined,
|
|
1438
|
+
romanizedText: key ? transliterations[key] : undefined,
|
|
1439
|
+
oppositeTurn: alignment === 'end',
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
return lines;
|
|
1443
|
+
}
|
|
1444
|
+
catch (e) {
|
|
1445
|
+
// eslint-disable-next-line no-console
|
|
1446
|
+
console.error('Failed to parse TTML', e);
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1088
1450
|
static convertKPoeLyrics(payload) {
|
|
1089
1451
|
if (!payload) {
|
|
1090
1452
|
return null;
|
|
@@ -1106,45 +1468,21 @@ class AmLyrics extends i {
|
|
|
1106
1468
|
const lines = [];
|
|
1107
1469
|
// If type is 'Line', we revert to line-by-line highlighting by skipping syllabus parsing
|
|
1108
1470
|
const isLineType = payload.type === 'Line' || payload.type === 'line';
|
|
1109
|
-
// Convert metadata.agents to
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
const personAgents = agentEntries.filter(
|
|
1116
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1117
|
-
([_, agentData]) => agentData.type === 'person');
|
|
1118
|
-
const personIndexMap = new Map();
|
|
1119
|
-
personAgents.forEach(([agentKey], personIndex) => {
|
|
1120
|
-
personIndexMap.set(agentKey, personIndex);
|
|
1121
|
-
});
|
|
1122
|
-
agentEntries.forEach(([agentKey, agentData]) => {
|
|
1123
|
-
const mappedKey = agentData.alias || agentKey;
|
|
1124
|
-
if (agentData.type === 'group') {
|
|
1125
|
-
singerAlignmentMap[mappedKey] = 'start';
|
|
1126
|
-
}
|
|
1127
|
-
else if (agentData.type === 'other') {
|
|
1128
|
-
singerAlignmentMap[mappedKey] = 'end';
|
|
1129
|
-
}
|
|
1130
|
-
else if (agentData.type === 'person') {
|
|
1131
|
-
const personIndex = personIndexMap.get(agentKey);
|
|
1132
|
-
if (personIndex !== undefined) {
|
|
1133
|
-
singerAlignmentMap[mappedKey] =
|
|
1134
|
-
personIndex % 2 === 0 ? 'start' : 'end';
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1471
|
+
// Convert metadata.agents to type map
|
|
1472
|
+
const agentTypes = {};
|
|
1473
|
+
if (payload.metadata?.agents) {
|
|
1474
|
+
Object.entries(payload.metadata.agents).forEach(([key, agent]) => {
|
|
1475
|
+
const mappedKey = agent.alias || key;
|
|
1476
|
+
agentTypes[mappedKey] = agent.type;
|
|
1137
1477
|
});
|
|
1138
1478
|
}
|
|
1139
|
-
|
|
1479
|
+
const lineSingers = sanitizedEntries.map((entry) => entry.element?.singer);
|
|
1480
|
+
const alignments = AmLyrics.calculateLineAlignments(lineSingers, agentTypes);
|
|
1481
|
+
for (let i = 0; i < sanitizedEntries.length; i += 1) {
|
|
1482
|
+
const entry = sanitizedEntries[i];
|
|
1140
1483
|
const start = AmLyrics.toMilliseconds(entry.time);
|
|
1141
1484
|
const duration = AmLyrics.toMilliseconds(entry.duration);
|
|
1142
|
-
|
|
1143
|
-
let alignment;
|
|
1144
|
-
const singerId = entry.element?.singer;
|
|
1145
|
-
if (singerId && singerAlignmentMap[singerId]) {
|
|
1146
|
-
alignment = singerAlignmentMap[singerId];
|
|
1147
|
-
}
|
|
1485
|
+
const alignment = alignments[i];
|
|
1148
1486
|
const lineText = typeof entry.text === 'string' ? entry.text : '';
|
|
1149
1487
|
const lineStart = AmLyrics.toMilliseconds(entry.time);
|
|
1150
1488
|
const lineDuration = AmLyrics.toMilliseconds(entry.duration);
|
|
@@ -1195,10 +1533,8 @@ class AmLyrics extends i {
|
|
|
1195
1533
|
// If syllabus data matches, map it to main syllables
|
|
1196
1534
|
if (Array.isArray(transliteration.syllabus) &&
|
|
1197
1535
|
transliteration.syllabus.length === mainSyllables.length) {
|
|
1198
|
-
transliteration.syllabus.forEach((s,
|
|
1199
|
-
|
|
1200
|
-
mainSyllables[i].romanizedText = s.text;
|
|
1201
|
-
}
|
|
1536
|
+
transliteration.syllabus.forEach((s, idx) => {
|
|
1537
|
+
mainSyllables[idx].romanizedText = s.text;
|
|
1202
1538
|
});
|
|
1203
1539
|
}
|
|
1204
1540
|
}
|
|
@@ -1208,10 +1544,11 @@ class AmLyrics extends i {
|
|
|
1208
1544
|
text: mainSyllables,
|
|
1209
1545
|
background: backgroundSyllables.length > 0,
|
|
1210
1546
|
backgroundText: backgroundSyllables,
|
|
1211
|
-
oppositeTurn:
|
|
1212
|
-
|
|
1213
|
-
entry.element.includes('
|
|
1214
|
-
|
|
1547
|
+
oppositeTurn: alignment === 'end' ||
|
|
1548
|
+
(Array.isArray(entry.element)
|
|
1549
|
+
? entry.element.includes('opposite') ||
|
|
1550
|
+
entry.element.includes('right')
|
|
1551
|
+
: false),
|
|
1215
1552
|
timestamp: lineStart,
|
|
1216
1553
|
endtime: start + duration,
|
|
1217
1554
|
isWordSynced: isLineType ? false : hasWordSync,
|