@lightbird/core 0.5.0 → 0.6.0
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/dist/index.cjs +190 -43
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +190 -44
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -239,6 +239,121 @@ function parseVttTimestamp(ts) {
|
|
|
239
239
|
return NaN;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
// src/utils/language-names.ts
|
|
243
|
+
var LANGUAGE_NAMES = {
|
|
244
|
+
// English
|
|
245
|
+
en: "English",
|
|
246
|
+
eng: "English",
|
|
247
|
+
// Japanese
|
|
248
|
+
ja: "Japanese",
|
|
249
|
+
jpn: "Japanese",
|
|
250
|
+
// Chinese
|
|
251
|
+
zh: "Chinese",
|
|
252
|
+
chi: "Chinese",
|
|
253
|
+
zho: "Chinese",
|
|
254
|
+
// Korean
|
|
255
|
+
ko: "Korean",
|
|
256
|
+
kor: "Korean",
|
|
257
|
+
// French
|
|
258
|
+
fr: "French",
|
|
259
|
+
fre: "French",
|
|
260
|
+
fra: "French",
|
|
261
|
+
// German
|
|
262
|
+
de: "German",
|
|
263
|
+
ger: "German",
|
|
264
|
+
deu: "German",
|
|
265
|
+
// Spanish
|
|
266
|
+
es: "Spanish",
|
|
267
|
+
spa: "Spanish",
|
|
268
|
+
// Italian
|
|
269
|
+
it: "Italian",
|
|
270
|
+
ita: "Italian",
|
|
271
|
+
// Portuguese
|
|
272
|
+
pt: "Portuguese",
|
|
273
|
+
por: "Portuguese",
|
|
274
|
+
// Russian
|
|
275
|
+
ru: "Russian",
|
|
276
|
+
rus: "Russian",
|
|
277
|
+
// Dutch
|
|
278
|
+
nl: "Dutch",
|
|
279
|
+
dut: "Dutch",
|
|
280
|
+
nld: "Dutch",
|
|
281
|
+
// Polish
|
|
282
|
+
pl: "Polish",
|
|
283
|
+
pol: "Polish",
|
|
284
|
+
// Arabic
|
|
285
|
+
ar: "Arabic",
|
|
286
|
+
ara: "Arabic",
|
|
287
|
+
// Hindi
|
|
288
|
+
hi: "Hindi",
|
|
289
|
+
hin: "Hindi",
|
|
290
|
+
// Bengali
|
|
291
|
+
bn: "Bengali",
|
|
292
|
+
ben: "Bengali",
|
|
293
|
+
// Turkish
|
|
294
|
+
tr: "Turkish",
|
|
295
|
+
tur: "Turkish",
|
|
296
|
+
// Swedish
|
|
297
|
+
sv: "Swedish",
|
|
298
|
+
swe: "Swedish",
|
|
299
|
+
// Norwegian
|
|
300
|
+
no: "Norwegian",
|
|
301
|
+
nor: "Norwegian",
|
|
302
|
+
// Danish
|
|
303
|
+
da: "Danish",
|
|
304
|
+
dan: "Danish",
|
|
305
|
+
// Finnish
|
|
306
|
+
fi: "Finnish",
|
|
307
|
+
fin: "Finnish",
|
|
308
|
+
// Greek
|
|
309
|
+
el: "Greek",
|
|
310
|
+
gre: "Greek",
|
|
311
|
+
ell: "Greek",
|
|
312
|
+
// Hebrew
|
|
313
|
+
he: "Hebrew",
|
|
314
|
+
heb: "Hebrew",
|
|
315
|
+
// Thai
|
|
316
|
+
th: "Thai",
|
|
317
|
+
tha: "Thai",
|
|
318
|
+
// Vietnamese
|
|
319
|
+
vi: "Vietnamese",
|
|
320
|
+
vie: "Vietnamese",
|
|
321
|
+
// Indonesian
|
|
322
|
+
id: "Indonesian",
|
|
323
|
+
ind: "Indonesian",
|
|
324
|
+
// Malay
|
|
325
|
+
ms: "Malay",
|
|
326
|
+
may: "Malay",
|
|
327
|
+
msa: "Malay",
|
|
328
|
+
// Czech
|
|
329
|
+
cs: "Czech",
|
|
330
|
+
cze: "Czech",
|
|
331
|
+
ces: "Czech",
|
|
332
|
+
// Hungarian
|
|
333
|
+
hu: "Hungarian",
|
|
334
|
+
hun: "Hungarian",
|
|
335
|
+
// Romanian
|
|
336
|
+
ro: "Romanian",
|
|
337
|
+
rum: "Romanian",
|
|
338
|
+
ron: "Romanian",
|
|
339
|
+
// Ukrainian
|
|
340
|
+
uk: "Ukrainian",
|
|
341
|
+
ukr: "Ukrainian",
|
|
342
|
+
// Tamil
|
|
343
|
+
ta: "Tamil",
|
|
344
|
+
tam: "Tamil",
|
|
345
|
+
// Telugu
|
|
346
|
+
te: "Telugu",
|
|
347
|
+
tel: "Telugu"
|
|
348
|
+
};
|
|
349
|
+
var UNDETERMINED = /* @__PURE__ */ new Set(["", "und", "unknown", "mis", "zxx", "mul"]);
|
|
350
|
+
function getLanguageName(code) {
|
|
351
|
+
if (!code) return void 0;
|
|
352
|
+
const normalized = code.trim().toLowerCase();
|
|
353
|
+
if (UNDETERMINED.has(normalized)) return void 0;
|
|
354
|
+
return LANGUAGE_NAMES[normalized] ?? code.trim();
|
|
355
|
+
}
|
|
356
|
+
|
|
242
357
|
// src/players/mkv-player.ts
|
|
243
358
|
async function canPlayNatively(objectUrl, timeoutMs = 3e3) {
|
|
244
359
|
return new Promise((resolve) => {
|
|
@@ -267,23 +382,76 @@ function parseStreamInfo(logs) {
|
|
|
267
382
|
const videoTracks = [];
|
|
268
383
|
const audioTracks = [];
|
|
269
384
|
const subtitleTracks = [];
|
|
385
|
+
let current = null;
|
|
270
386
|
const lines = logs.split("\n");
|
|
271
387
|
for (const line of lines) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
388
|
+
if (/^\s*Stream #\d+:\d+/.test(line)) {
|
|
389
|
+
current = null;
|
|
390
|
+
}
|
|
391
|
+
const streamMatch = line.match(
|
|
392
|
+
/Stream #\d+:\d+(?:\((\w+)\))?: (Video|Audio|Subtitle): (.+)$/i
|
|
393
|
+
);
|
|
394
|
+
if (streamMatch) {
|
|
395
|
+
const [, lang, type, rest] = streamMatch;
|
|
396
|
+
const codec = rest.trim().split(/[\s,]/)[0];
|
|
397
|
+
const forced = /\(forced\)/i.test(rest);
|
|
398
|
+
const isDefault = /\(default\)/i.test(rest);
|
|
399
|
+
const t = type.toLowerCase();
|
|
400
|
+
const bucket = t === "video" ? videoTracks : t === "audio" ? audioTracks : subtitleTracks;
|
|
401
|
+
current = {
|
|
402
|
+
index: bucket.length,
|
|
403
|
+
type: t,
|
|
404
|
+
codec,
|
|
405
|
+
lang,
|
|
406
|
+
forced,
|
|
407
|
+
default: isDefault
|
|
408
|
+
};
|
|
409
|
+
bucket.push(current);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (/^\s*(Chapter #|Program )/i.test(line)) {
|
|
413
|
+
current = null;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (current && current.title === void 0) {
|
|
417
|
+
const titleMatch = line.match(/^\s*title\s*:\s*(.+)$/i);
|
|
418
|
+
if (titleMatch) {
|
|
419
|
+
current.title = titleMatch[1].trim();
|
|
420
|
+
}
|
|
283
421
|
}
|
|
284
422
|
}
|
|
285
423
|
return { videoTracks, audioTracks, subtitleTracks };
|
|
286
424
|
}
|
|
425
|
+
function formatTrackName(index, t) {
|
|
426
|
+
let label = t.title?.trim() || `Track ${index + 1}`;
|
|
427
|
+
const langName = getLanguageName(t.lang);
|
|
428
|
+
if (langName) label += ` - [${langName}]`;
|
|
429
|
+
if (t.forced) label += " [Forced]";
|
|
430
|
+
return label;
|
|
431
|
+
}
|
|
432
|
+
function buildAudioTracks(tracks) {
|
|
433
|
+
if (tracks.length === 0) {
|
|
434
|
+
return [{ id: "0", name: "Default Audio", lang: "unknown" }];
|
|
435
|
+
}
|
|
436
|
+
return tracks.map((t, i) => ({
|
|
437
|
+
id: String(i),
|
|
438
|
+
name: formatTrackName(i, t),
|
|
439
|
+
lang: t.lang ?? "unknown"
|
|
440
|
+
}));
|
|
441
|
+
}
|
|
442
|
+
function buildSubtitleTracks(tracks, trackMap) {
|
|
443
|
+
trackMap.clear();
|
|
444
|
+
return tracks.map((t, i) => {
|
|
445
|
+
const id = String(i);
|
|
446
|
+
trackMap.set(id, i);
|
|
447
|
+
return {
|
|
448
|
+
id,
|
|
449
|
+
name: formatTrackName(i, t),
|
|
450
|
+
lang: t.lang ?? "unknown",
|
|
451
|
+
type: "embedded"
|
|
452
|
+
};
|
|
453
|
+
});
|
|
454
|
+
}
|
|
287
455
|
var _MKVPlayer = class _MKVPlayer {
|
|
288
456
|
constructor(file, onProgress) {
|
|
289
457
|
this.videoElement = null;
|
|
@@ -402,22 +570,11 @@ var _MKVPlayer = class _MKVPlayer {
|
|
|
402
570
|
});
|
|
403
571
|
const { audioTracks, subtitleTracks } = parseStreamInfo(result.logs);
|
|
404
572
|
this.chapters = parseChaptersFromFFmpegLog(result.logs, videoElement.duration || 0);
|
|
405
|
-
this.playerFile.audioTracks = audioTracks
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
this.subtitleTrackMap.clear();
|
|
411
|
-
this.playerFile.subtitleTracks = subtitleTracks.map((t, i) => {
|
|
412
|
-
const id = String(i);
|
|
413
|
-
this.subtitleTrackMap.set(id, i);
|
|
414
|
-
return {
|
|
415
|
-
id,
|
|
416
|
-
name: t.title ?? (t.lang ? `Subtitle ${i + 1} (${t.lang})` : `Subtitle ${i + 1}`),
|
|
417
|
-
lang: t.lang ?? "unknown",
|
|
418
|
-
type: "embedded"
|
|
419
|
-
};
|
|
420
|
-
});
|
|
573
|
+
this.playerFile.audioTracks = buildAudioTracks(audioTracks);
|
|
574
|
+
this.playerFile.subtitleTracks = buildSubtitleTracks(
|
|
575
|
+
subtitleTracks,
|
|
576
|
+
this.subtitleTrackMap
|
|
577
|
+
);
|
|
421
578
|
const blob = new Blob([result.data], { type: "video/mp4" });
|
|
422
579
|
const url = URL.createObjectURL(blob);
|
|
423
580
|
this.remuxCache.set(0, url);
|
|
@@ -448,22 +605,11 @@ var _MKVPlayer = class _MKVPlayer {
|
|
|
448
605
|
});
|
|
449
606
|
if (this._cancelled) return;
|
|
450
607
|
const { audioTracks, subtitleTracks } = parseStreamInfo(probeResult.logs);
|
|
451
|
-
this.playerFile.audioTracks = audioTracks
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
this.subtitleTrackMap.clear();
|
|
457
|
-
this.playerFile.subtitleTracks = subtitleTracks.map((t, i) => {
|
|
458
|
-
const id = String(i);
|
|
459
|
-
this.subtitleTrackMap.set(id, i);
|
|
460
|
-
return {
|
|
461
|
-
id,
|
|
462
|
-
name: t.title ?? (t.lang ? `Subtitle ${i + 1} (${t.lang})` : `Subtitle ${i + 1}`),
|
|
463
|
-
lang: t.lang ?? "unknown",
|
|
464
|
-
type: "embedded"
|
|
465
|
-
};
|
|
466
|
-
});
|
|
608
|
+
this.playerFile.audioTracks = buildAudioTracks(audioTracks);
|
|
609
|
+
this.playerFile.subtitleTracks = buildSubtitleTracks(
|
|
610
|
+
subtitleTracks,
|
|
611
|
+
this.subtitleTrackMap
|
|
612
|
+
);
|
|
467
613
|
}
|
|
468
614
|
async _remux(audioTrackIndex) {
|
|
469
615
|
const cached = this.remuxCache.get(audioTrackIndex);
|
|
@@ -1620,6 +1766,7 @@ exports.exportPlaylist = exportPlaylist;
|
|
|
1620
1766
|
exports.extractNativeMetadata = extractNativeMetadata;
|
|
1621
1767
|
exports.formatShortcutKey = formatShortcutKey;
|
|
1622
1768
|
exports.getFFmpeg = getFFmpeg;
|
|
1769
|
+
exports.getLanguageName = getLanguageName;
|
|
1623
1770
|
exports.getVideoFiles = getVideoFiles;
|
|
1624
1771
|
exports.getWebTorrentClient = getWebTorrentClient;
|
|
1625
1772
|
exports.hasAcceptedDisclaimer = hasAcceptedDisclaimer;
|
package/dist/index.d.cts
CHANGED
|
@@ -441,4 +441,13 @@ declare class ProgressEstimator {
|
|
|
441
441
|
declare function getFFmpeg(): Promise<FFmpeg>;
|
|
442
442
|
declare function resetFFmpeg(): void;
|
|
443
443
|
|
|
444
|
-
|
|
444
|
+
/**
|
|
445
|
+
* Resolve an ISO 639 language code to its English language name, mirroring how
|
|
446
|
+
* VLC labels tracks.
|
|
447
|
+
*
|
|
448
|
+
* @returns The full language name, or the original code if it's unrecognised
|
|
449
|
+
* (VLC behaviour), or `undefined` if the code is absent/undetermined.
|
|
450
|
+
*/
|
|
451
|
+
declare function getLanguageName(code?: string | null): string | undefined;
|
|
452
|
+
|
|
453
|
+
export { ASSRenderer, type AudioTrack, type AudioTrackMeta, CancellationError, type Chapter, DEFAULT_SHORTCUTS, DEFAULT_TRACKERS, DISCLAIMER_KEY, FLAG_MAGNET_LINK, HLSPlayer, type HLSPlayerFile, type LightBirdConfig, MKVPlayer, type MKVPlayerFile, type MediaErrorType, type ParsedMediaError, type PlaylistItem, type ProcessedFile, ProgressEstimator, type QualityLevel, type ShortcutAction, type ShortcutBinding, SimplePlayer, type SimplePlayerFile, type Subtitle, SubtitleConverter, type SubtitleCue, type SubtitleTrackMeta, type TorrentStatus, UniversalSubtitleManager, VIDEO_EXTENSIONS, type VideoFilters, type VideoMetadata, type VideoPlayer, acceptDisclaimer, applyOffsetToVtt, captureFrameAt, captureVideoThumbnail, configureLightBird, createOffsetVttUrl, createVideoPlayer, destroyWebTorrentClient, exportPlaylist, extractNativeMetadata, formatShortcutKey, getFFmpeg, getLanguageName, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isHlsUrl, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
|
package/dist/index.d.ts
CHANGED
|
@@ -441,4 +441,13 @@ declare class ProgressEstimator {
|
|
|
441
441
|
declare function getFFmpeg(): Promise<FFmpeg>;
|
|
442
442
|
declare function resetFFmpeg(): void;
|
|
443
443
|
|
|
444
|
-
|
|
444
|
+
/**
|
|
445
|
+
* Resolve an ISO 639 language code to its English language name, mirroring how
|
|
446
|
+
* VLC labels tracks.
|
|
447
|
+
*
|
|
448
|
+
* @returns The full language name, or the original code if it's unrecognised
|
|
449
|
+
* (VLC behaviour), or `undefined` if the code is absent/undetermined.
|
|
450
|
+
*/
|
|
451
|
+
declare function getLanguageName(code?: string | null): string | undefined;
|
|
452
|
+
|
|
453
|
+
export { ASSRenderer, type AudioTrack, type AudioTrackMeta, CancellationError, type Chapter, DEFAULT_SHORTCUTS, DEFAULT_TRACKERS, DISCLAIMER_KEY, FLAG_MAGNET_LINK, HLSPlayer, type HLSPlayerFile, type LightBirdConfig, MKVPlayer, type MKVPlayerFile, type MediaErrorType, type ParsedMediaError, type PlaylistItem, type ProcessedFile, ProgressEstimator, type QualityLevel, type ShortcutAction, type ShortcutBinding, SimplePlayer, type SimplePlayerFile, type Subtitle, SubtitleConverter, type SubtitleCue, type SubtitleTrackMeta, type TorrentStatus, UniversalSubtitleManager, VIDEO_EXTENSIONS, type VideoFilters, type VideoMetadata, type VideoPlayer, acceptDisclaimer, applyOffsetToVtt, captureFrameAt, captureVideoThumbnail, configureLightBird, createOffsetVttUrl, createVideoPlayer, destroyWebTorrentClient, exportPlaylist, extractNativeMetadata, formatShortcutKey, getFFmpeg, getLanguageName, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isHlsUrl, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
|
package/dist/index.js
CHANGED
|
@@ -236,6 +236,121 @@ function parseVttTimestamp(ts) {
|
|
|
236
236
|
return NaN;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
// src/utils/language-names.ts
|
|
240
|
+
var LANGUAGE_NAMES = {
|
|
241
|
+
// English
|
|
242
|
+
en: "English",
|
|
243
|
+
eng: "English",
|
|
244
|
+
// Japanese
|
|
245
|
+
ja: "Japanese",
|
|
246
|
+
jpn: "Japanese",
|
|
247
|
+
// Chinese
|
|
248
|
+
zh: "Chinese",
|
|
249
|
+
chi: "Chinese",
|
|
250
|
+
zho: "Chinese",
|
|
251
|
+
// Korean
|
|
252
|
+
ko: "Korean",
|
|
253
|
+
kor: "Korean",
|
|
254
|
+
// French
|
|
255
|
+
fr: "French",
|
|
256
|
+
fre: "French",
|
|
257
|
+
fra: "French",
|
|
258
|
+
// German
|
|
259
|
+
de: "German",
|
|
260
|
+
ger: "German",
|
|
261
|
+
deu: "German",
|
|
262
|
+
// Spanish
|
|
263
|
+
es: "Spanish",
|
|
264
|
+
spa: "Spanish",
|
|
265
|
+
// Italian
|
|
266
|
+
it: "Italian",
|
|
267
|
+
ita: "Italian",
|
|
268
|
+
// Portuguese
|
|
269
|
+
pt: "Portuguese",
|
|
270
|
+
por: "Portuguese",
|
|
271
|
+
// Russian
|
|
272
|
+
ru: "Russian",
|
|
273
|
+
rus: "Russian",
|
|
274
|
+
// Dutch
|
|
275
|
+
nl: "Dutch",
|
|
276
|
+
dut: "Dutch",
|
|
277
|
+
nld: "Dutch",
|
|
278
|
+
// Polish
|
|
279
|
+
pl: "Polish",
|
|
280
|
+
pol: "Polish",
|
|
281
|
+
// Arabic
|
|
282
|
+
ar: "Arabic",
|
|
283
|
+
ara: "Arabic",
|
|
284
|
+
// Hindi
|
|
285
|
+
hi: "Hindi",
|
|
286
|
+
hin: "Hindi",
|
|
287
|
+
// Bengali
|
|
288
|
+
bn: "Bengali",
|
|
289
|
+
ben: "Bengali",
|
|
290
|
+
// Turkish
|
|
291
|
+
tr: "Turkish",
|
|
292
|
+
tur: "Turkish",
|
|
293
|
+
// Swedish
|
|
294
|
+
sv: "Swedish",
|
|
295
|
+
swe: "Swedish",
|
|
296
|
+
// Norwegian
|
|
297
|
+
no: "Norwegian",
|
|
298
|
+
nor: "Norwegian",
|
|
299
|
+
// Danish
|
|
300
|
+
da: "Danish",
|
|
301
|
+
dan: "Danish",
|
|
302
|
+
// Finnish
|
|
303
|
+
fi: "Finnish",
|
|
304
|
+
fin: "Finnish",
|
|
305
|
+
// Greek
|
|
306
|
+
el: "Greek",
|
|
307
|
+
gre: "Greek",
|
|
308
|
+
ell: "Greek",
|
|
309
|
+
// Hebrew
|
|
310
|
+
he: "Hebrew",
|
|
311
|
+
heb: "Hebrew",
|
|
312
|
+
// Thai
|
|
313
|
+
th: "Thai",
|
|
314
|
+
tha: "Thai",
|
|
315
|
+
// Vietnamese
|
|
316
|
+
vi: "Vietnamese",
|
|
317
|
+
vie: "Vietnamese",
|
|
318
|
+
// Indonesian
|
|
319
|
+
id: "Indonesian",
|
|
320
|
+
ind: "Indonesian",
|
|
321
|
+
// Malay
|
|
322
|
+
ms: "Malay",
|
|
323
|
+
may: "Malay",
|
|
324
|
+
msa: "Malay",
|
|
325
|
+
// Czech
|
|
326
|
+
cs: "Czech",
|
|
327
|
+
cze: "Czech",
|
|
328
|
+
ces: "Czech",
|
|
329
|
+
// Hungarian
|
|
330
|
+
hu: "Hungarian",
|
|
331
|
+
hun: "Hungarian",
|
|
332
|
+
// Romanian
|
|
333
|
+
ro: "Romanian",
|
|
334
|
+
rum: "Romanian",
|
|
335
|
+
ron: "Romanian",
|
|
336
|
+
// Ukrainian
|
|
337
|
+
uk: "Ukrainian",
|
|
338
|
+
ukr: "Ukrainian",
|
|
339
|
+
// Tamil
|
|
340
|
+
ta: "Tamil",
|
|
341
|
+
tam: "Tamil",
|
|
342
|
+
// Telugu
|
|
343
|
+
te: "Telugu",
|
|
344
|
+
tel: "Telugu"
|
|
345
|
+
};
|
|
346
|
+
var UNDETERMINED = /* @__PURE__ */ new Set(["", "und", "unknown", "mis", "zxx", "mul"]);
|
|
347
|
+
function getLanguageName(code) {
|
|
348
|
+
if (!code) return void 0;
|
|
349
|
+
const normalized = code.trim().toLowerCase();
|
|
350
|
+
if (UNDETERMINED.has(normalized)) return void 0;
|
|
351
|
+
return LANGUAGE_NAMES[normalized] ?? code.trim();
|
|
352
|
+
}
|
|
353
|
+
|
|
239
354
|
// src/players/mkv-player.ts
|
|
240
355
|
async function canPlayNatively(objectUrl, timeoutMs = 3e3) {
|
|
241
356
|
return new Promise((resolve) => {
|
|
@@ -264,23 +379,76 @@ function parseStreamInfo(logs) {
|
|
|
264
379
|
const videoTracks = [];
|
|
265
380
|
const audioTracks = [];
|
|
266
381
|
const subtitleTracks = [];
|
|
382
|
+
let current = null;
|
|
267
383
|
const lines = logs.split("\n");
|
|
268
384
|
for (const line of lines) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
385
|
+
if (/^\s*Stream #\d+:\d+/.test(line)) {
|
|
386
|
+
current = null;
|
|
387
|
+
}
|
|
388
|
+
const streamMatch = line.match(
|
|
389
|
+
/Stream #\d+:\d+(?:\((\w+)\))?: (Video|Audio|Subtitle): (.+)$/i
|
|
390
|
+
);
|
|
391
|
+
if (streamMatch) {
|
|
392
|
+
const [, lang, type, rest] = streamMatch;
|
|
393
|
+
const codec = rest.trim().split(/[\s,]/)[0];
|
|
394
|
+
const forced = /\(forced\)/i.test(rest);
|
|
395
|
+
const isDefault = /\(default\)/i.test(rest);
|
|
396
|
+
const t = type.toLowerCase();
|
|
397
|
+
const bucket = t === "video" ? videoTracks : t === "audio" ? audioTracks : subtitleTracks;
|
|
398
|
+
current = {
|
|
399
|
+
index: bucket.length,
|
|
400
|
+
type: t,
|
|
401
|
+
codec,
|
|
402
|
+
lang,
|
|
403
|
+
forced,
|
|
404
|
+
default: isDefault
|
|
405
|
+
};
|
|
406
|
+
bucket.push(current);
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (/^\s*(Chapter #|Program )/i.test(line)) {
|
|
410
|
+
current = null;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (current && current.title === void 0) {
|
|
414
|
+
const titleMatch = line.match(/^\s*title\s*:\s*(.+)$/i);
|
|
415
|
+
if (titleMatch) {
|
|
416
|
+
current.title = titleMatch[1].trim();
|
|
417
|
+
}
|
|
280
418
|
}
|
|
281
419
|
}
|
|
282
420
|
return { videoTracks, audioTracks, subtitleTracks };
|
|
283
421
|
}
|
|
422
|
+
function formatTrackName(index, t) {
|
|
423
|
+
let label = t.title?.trim() || `Track ${index + 1}`;
|
|
424
|
+
const langName = getLanguageName(t.lang);
|
|
425
|
+
if (langName) label += ` - [${langName}]`;
|
|
426
|
+
if (t.forced) label += " [Forced]";
|
|
427
|
+
return label;
|
|
428
|
+
}
|
|
429
|
+
function buildAudioTracks(tracks) {
|
|
430
|
+
if (tracks.length === 0) {
|
|
431
|
+
return [{ id: "0", name: "Default Audio", lang: "unknown" }];
|
|
432
|
+
}
|
|
433
|
+
return tracks.map((t, i) => ({
|
|
434
|
+
id: String(i),
|
|
435
|
+
name: formatTrackName(i, t),
|
|
436
|
+
lang: t.lang ?? "unknown"
|
|
437
|
+
}));
|
|
438
|
+
}
|
|
439
|
+
function buildSubtitleTracks(tracks, trackMap) {
|
|
440
|
+
trackMap.clear();
|
|
441
|
+
return tracks.map((t, i) => {
|
|
442
|
+
const id = String(i);
|
|
443
|
+
trackMap.set(id, i);
|
|
444
|
+
return {
|
|
445
|
+
id,
|
|
446
|
+
name: formatTrackName(i, t),
|
|
447
|
+
lang: t.lang ?? "unknown",
|
|
448
|
+
type: "embedded"
|
|
449
|
+
};
|
|
450
|
+
});
|
|
451
|
+
}
|
|
284
452
|
var _MKVPlayer = class _MKVPlayer {
|
|
285
453
|
constructor(file, onProgress) {
|
|
286
454
|
this.videoElement = null;
|
|
@@ -399,22 +567,11 @@ var _MKVPlayer = class _MKVPlayer {
|
|
|
399
567
|
});
|
|
400
568
|
const { audioTracks, subtitleTracks } = parseStreamInfo(result.logs);
|
|
401
569
|
this.chapters = parseChaptersFromFFmpegLog(result.logs, videoElement.duration || 0);
|
|
402
|
-
this.playerFile.audioTracks = audioTracks
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
this.subtitleTrackMap.clear();
|
|
408
|
-
this.playerFile.subtitleTracks = subtitleTracks.map((t, i) => {
|
|
409
|
-
const id = String(i);
|
|
410
|
-
this.subtitleTrackMap.set(id, i);
|
|
411
|
-
return {
|
|
412
|
-
id,
|
|
413
|
-
name: t.title ?? (t.lang ? `Subtitle ${i + 1} (${t.lang})` : `Subtitle ${i + 1}`),
|
|
414
|
-
lang: t.lang ?? "unknown",
|
|
415
|
-
type: "embedded"
|
|
416
|
-
};
|
|
417
|
-
});
|
|
570
|
+
this.playerFile.audioTracks = buildAudioTracks(audioTracks);
|
|
571
|
+
this.playerFile.subtitleTracks = buildSubtitleTracks(
|
|
572
|
+
subtitleTracks,
|
|
573
|
+
this.subtitleTrackMap
|
|
574
|
+
);
|
|
418
575
|
const blob = new Blob([result.data], { type: "video/mp4" });
|
|
419
576
|
const url = URL.createObjectURL(blob);
|
|
420
577
|
this.remuxCache.set(0, url);
|
|
@@ -445,22 +602,11 @@ var _MKVPlayer = class _MKVPlayer {
|
|
|
445
602
|
});
|
|
446
603
|
if (this._cancelled) return;
|
|
447
604
|
const { audioTracks, subtitleTracks } = parseStreamInfo(probeResult.logs);
|
|
448
|
-
this.playerFile.audioTracks = audioTracks
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
this.subtitleTrackMap.clear();
|
|
454
|
-
this.playerFile.subtitleTracks = subtitleTracks.map((t, i) => {
|
|
455
|
-
const id = String(i);
|
|
456
|
-
this.subtitleTrackMap.set(id, i);
|
|
457
|
-
return {
|
|
458
|
-
id,
|
|
459
|
-
name: t.title ?? (t.lang ? `Subtitle ${i + 1} (${t.lang})` : `Subtitle ${i + 1}`),
|
|
460
|
-
lang: t.lang ?? "unknown",
|
|
461
|
-
type: "embedded"
|
|
462
|
-
};
|
|
463
|
-
});
|
|
605
|
+
this.playerFile.audioTracks = buildAudioTracks(audioTracks);
|
|
606
|
+
this.playerFile.subtitleTracks = buildSubtitleTracks(
|
|
607
|
+
subtitleTracks,
|
|
608
|
+
this.subtitleTrackMap
|
|
609
|
+
);
|
|
464
610
|
}
|
|
465
611
|
async _remux(audioTrackIndex) {
|
|
466
612
|
const cached = this.remuxCache.get(audioTrackIndex);
|
|
@@ -1592,4 +1738,4 @@ function resetFFmpeg() {
|
|
|
1592
1738
|
loading = null;
|
|
1593
1739
|
}
|
|
1594
1740
|
|
|
1595
|
-
export { ASSRenderer, CancellationError, DEFAULT_SHORTCUTS, DEFAULT_TRACKERS, DISCLAIMER_KEY, FLAG_MAGNET_LINK, HLSPlayer, MKVPlayer, ProgressEstimator, SimplePlayer, SubtitleConverter, UniversalSubtitleManager, VIDEO_EXTENSIONS, acceptDisclaimer, applyOffsetToVtt, captureFrameAt, captureVideoThumbnail, configureLightBird, createOffsetVttUrl, createVideoPlayer, destroyWebTorrentClient, exportPlaylist, extractNativeMetadata, formatShortcutKey, getFFmpeg, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isHlsUrl, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
|
|
1741
|
+
export { ASSRenderer, CancellationError, DEFAULT_SHORTCUTS, DEFAULT_TRACKERS, DISCLAIMER_KEY, FLAG_MAGNET_LINK, HLSPlayer, MKVPlayer, ProgressEstimator, SimplePlayer, SubtitleConverter, UniversalSubtitleManager, VIDEO_EXTENSIONS, acceptDisclaimer, applyOffsetToVtt, captureFrameAt, captureVideoThumbnail, configureLightBird, createOffsetVttUrl, createVideoPlayer, destroyWebTorrentClient, exportPlaylist, extractNativeMetadata, formatShortcutKey, getFFmpeg, getLanguageName, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isHlsUrl, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightbird/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Client-side video player engine. Plays MKV, MP4, WebM with full subtitle, audio track, and chapter support. No server required.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Punyam Singh",
|