@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 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
- const streamMatch = line.match(/Stream #\d+:\d+(?:\((\w+)\))?: (Video|Audio|Subtitle): (\S+)/i);
273
- if (!streamMatch) continue;
274
- const [, lang, type, codec] = streamMatch;
275
- const titleMatch = line.match(/\btitle\s*:\s*([^,\n]+)/i);
276
- const title = titleMatch ? titleMatch[1].trim() : void 0;
277
- if (type.toLowerCase() === "video") {
278
- videoTracks.push({ index: videoTracks.length, type: "video", codec, lang, title });
279
- } else if (type.toLowerCase() === "audio") {
280
- audioTracks.push({ index: audioTracks.length, type: "audio", codec, lang, title });
281
- } else if (type.toLowerCase() === "subtitle") {
282
- subtitleTracks.push({ index: subtitleTracks.length, type: "subtitle", codec, lang, title });
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.length > 0 ? audioTracks.map((t, i) => ({
406
- id: String(i),
407
- name: t.title ?? (t.lang ? `Audio ${i + 1} (${t.lang})` : `Audio ${i + 1}`),
408
- lang: t.lang ?? "unknown"
409
- })) : [{ id: "0", name: "Default Audio", lang: "unknown" }];
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.length > 0 ? audioTracks.map((t, i) => ({
452
- id: String(i),
453
- name: t.title ?? (t.lang ? `Audio ${i + 1} (${t.lang})` : `Audio ${i + 1}`),
454
- lang: t.lang ?? "unknown"
455
- })) : [{ id: "0", name: "Default Audio", lang: "unknown" }];
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
- 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, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isHlsUrl, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
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
- 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, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isHlsUrl, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
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
- const streamMatch = line.match(/Stream #\d+:\d+(?:\((\w+)\))?: (Video|Audio|Subtitle): (\S+)/i);
270
- if (!streamMatch) continue;
271
- const [, lang, type, codec] = streamMatch;
272
- const titleMatch = line.match(/\btitle\s*:\s*([^,\n]+)/i);
273
- const title = titleMatch ? titleMatch[1].trim() : void 0;
274
- if (type.toLowerCase() === "video") {
275
- videoTracks.push({ index: videoTracks.length, type: "video", codec, lang, title });
276
- } else if (type.toLowerCase() === "audio") {
277
- audioTracks.push({ index: audioTracks.length, type: "audio", codec, lang, title });
278
- } else if (type.toLowerCase() === "subtitle") {
279
- subtitleTracks.push({ index: subtitleTracks.length, type: "subtitle", codec, lang, title });
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.length > 0 ? audioTracks.map((t, i) => ({
403
- id: String(i),
404
- name: t.title ?? (t.lang ? `Audio ${i + 1} (${t.lang})` : `Audio ${i + 1}`),
405
- lang: t.lang ?? "unknown"
406
- })) : [{ id: "0", name: "Default Audio", lang: "unknown" }];
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.length > 0 ? audioTracks.map((t, i) => ({
449
- id: String(i),
450
- name: t.title ?? (t.lang ? `Audio ${i + 1} (${t.lang})` : `Audio ${i + 1}`),
451
- lang: t.lang ?? "unknown"
452
- })) : [{ id: "0", name: "Default Audio", lang: "unknown" }];
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.5.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",