dasha 4.1.0 → 4.2.1
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/README.md +38 -0
- package/dist/index.d.mts +99 -47
- package/dist/index.mjs +459 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ async function saveVideo() {
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
const bestVideoTrack = videoTracks[0];
|
|
45
|
+
console.log('Dynamic range:', await bestVideoTrack.getDynamicRange()); // sdr
|
|
45
46
|
|
|
46
47
|
const segments = await bestVideoTrack.getSegments();
|
|
47
48
|
|
|
@@ -93,6 +94,43 @@ async function saveDashVideo() {
|
|
|
93
94
|
}
|
|
94
95
|
```
|
|
95
96
|
|
|
97
|
+
### Adding external subtitle tracks
|
|
98
|
+
|
|
99
|
+
`addSubtitleTrack()` is useful when subtitle URLs are provided separately from the HLS/DASH manifest. Added subtitles become part of the same `Input`, so they can be queried, filtered and downloaded through the regular subtitle track API.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { DASH_FORMATS, Input, UrlSource } from 'dasha';
|
|
103
|
+
|
|
104
|
+
async function getEnglishSubtitles() {
|
|
105
|
+
const input = new Input({
|
|
106
|
+
source: new UrlSource('https://example.com/manifest.mpd'),
|
|
107
|
+
formats: DASH_FORMATS,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const primaryVideoTrack = await input.getPrimaryVideoTrack();
|
|
111
|
+
if (!primaryVideoTrack) {
|
|
112
|
+
throw new Error('No video tracks found');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
input.addSubtitleTrack(new UrlSource('https://cdn.example.com/subtitles/en.vtt'), {
|
|
116
|
+
languageCode: 'en',
|
|
117
|
+
name: 'English',
|
|
118
|
+
pairWith: primaryVideoTrack,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const englishSubtitleTracks = await input.getSubtitleTracks({
|
|
122
|
+
filter: async (track) => (await track.getLanguageCode()) === 'en',
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const englishSubtitleTrack = englishSubtitleTracks[0];
|
|
126
|
+
if (!englishSubtitleTrack) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return await englishSubtitleTrack.getSegments();
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
96
134
|
### Mediabunny with DASH support
|
|
97
135
|
|
|
98
136
|
> Only reading is supported
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _$mediabunny from "mediabunny";
|
|
2
|
-
import { AudioCodec, DurationMetadataRequestOptions, EncodedPacket, FilePathSource, HLS_FORMATS, Input as Input$1, InputAudioTrack as InputAudioTrack$1, InputFormat, InputOptions, InputTrack as InputTrack$2, InputTrackQuery, InputTrackQuery as InputTrackQuery$1, InputVideoTrack as InputVideoTrack$1, MediaCodec as MediaCodec$1, MetadataTags, PacketRetrievalOptions, Source, TrackDisposition, UrlSource, VideoCodec, asc, desc, prefer } from "mediabunny";
|
|
2
|
+
import { AudioCodec as AudioCodec$1, DurationMetadataRequestOptions, EncodedPacket, FilePathSource, HLS, HLS_FORMATS, Input as Input$1, InputAudioTrack as InputAudioTrack$1, InputFormat, InputOptions, InputTrack as InputTrack$2, InputTrackQuery, InputTrackQuery as InputTrackQuery$1, InputVideoTrack as InputVideoTrack$1, MP3, MP4, MaybePromise, MediaCodec as MediaCodec$1, MetadataTags, PacketRetrievalOptions, PathedSource, Source, SourceRef, SubtitleCodec as SubtitleCodec$1, TrackDisposition, UrlSource, VideoCodec as VideoCodec$1, asc, desc, prefer } from "mediabunny";
|
|
3
3
|
import { Element } from "@xmldom/xmldom";
|
|
4
4
|
|
|
5
5
|
//#region src/mediabunny.d.ts
|
|
@@ -46,60 +46,47 @@ type InputTrackWithBacking = InputTrack & {
|
|
|
46
46
|
};
|
|
47
47
|
//#endregion
|
|
48
48
|
//#region src/codec.d.ts
|
|
49
|
-
/**
|
|
50
|
-
* List of known video codecs, ordered by encoding preference.
|
|
51
|
-
* @group Codecs
|
|
52
|
-
* @public
|
|
53
|
-
*/
|
|
54
|
-
declare const VIDEO_CODECS: readonly ["avc", "hevc", "vp8", "vp9", "av1", "vc1"];
|
|
55
|
-
/**
|
|
56
|
-
* List of known video dynamic ranges.
|
|
57
|
-
* @group Codecs
|
|
58
|
-
* @public
|
|
59
|
-
*/
|
|
60
|
-
declare const VIDEO_DYNAMIC_RANGES: readonly ["sdr", "hlg", "hdr10", "hdr10+", "dv"];
|
|
61
|
-
/**
|
|
62
|
-
* List of known audio codecs, ordered by encoding preference.
|
|
63
|
-
* @group Codecs
|
|
64
|
-
* @public
|
|
65
|
-
*/
|
|
66
|
-
declare const AUDIO_CODECS: readonly ["aac", "opus", "mp3", "vorbis", "flac", "alac", "ac3", "eac3", "dts"];
|
|
67
|
-
/**
|
|
68
|
-
* List of known subtitle codecs, ordered by encoding preference.
|
|
69
|
-
* @group Codecs
|
|
70
|
-
* @public
|
|
71
|
-
*/
|
|
72
|
-
declare const SUBTITLE_CODECS: readonly ["srt", "webvtt", "ttml", "dfxp", "ssa", "ass", "stpp"];
|
|
73
49
|
/**
|
|
74
50
|
* Union type of known video codecs.
|
|
75
51
|
* @group Codecs
|
|
76
52
|
* @public
|
|
77
53
|
*/
|
|
78
|
-
type VideoCodec$1
|
|
54
|
+
type VideoCodec = VideoCodec$1 | 'vc1';
|
|
79
55
|
/**
|
|
80
56
|
* Union type of known video dynamic ranges.
|
|
57
|
+
*
|
|
58
|
+
* `sdr`: Standard Dynamic Range
|
|
59
|
+
*
|
|
60
|
+
* `hlg`: High-Luminance Gamma
|
|
61
|
+
*
|
|
62
|
+
* `hdr10`: High-Dynamic Range 10
|
|
63
|
+
*
|
|
64
|
+
* `hdr10+`: High-Dynamic Range 10+
|
|
65
|
+
*
|
|
66
|
+
* `dv`: Dolby Vision
|
|
67
|
+
*
|
|
81
68
|
* @group Codecs
|
|
82
69
|
* @public
|
|
83
70
|
*/
|
|
84
|
-
type VideoDynamicRange =
|
|
71
|
+
type VideoDynamicRange = 'sdr' | 'hlg' | 'hdr10' | 'hdr10+' | 'dv';
|
|
85
72
|
/**
|
|
86
73
|
* Union type of known audio codecs.
|
|
87
74
|
* @group Codecs
|
|
88
75
|
* @public
|
|
89
76
|
*/
|
|
90
|
-
type AudioCodec$1
|
|
77
|
+
type AudioCodec = AudioCodec$1 | 'dts' | 'alac';
|
|
91
78
|
/**
|
|
92
79
|
* Union type of known subtitle codecs.
|
|
93
80
|
* @group Codecs
|
|
94
81
|
* @public
|
|
95
82
|
*/
|
|
96
|
-
type SubtitleCodec =
|
|
83
|
+
type SubtitleCodec = SubtitleCodec$1 | 'srt' | 'ttml' | 'dfxp' | 'ssa' | 'ass' | 'stpp';
|
|
97
84
|
/**
|
|
98
85
|
* Union type of known media codecs.
|
|
99
86
|
* @group Codecs
|
|
100
87
|
* @public
|
|
101
88
|
*/
|
|
102
|
-
type MediaCodec = VideoCodec
|
|
89
|
+
type MediaCodec = VideoCodec | AudioCodec | SubtitleCodec;
|
|
103
90
|
//#endregion
|
|
104
91
|
//#region src/role-type.d.ts
|
|
105
92
|
declare const ROLE_TYPE: {
|
|
@@ -171,6 +158,7 @@ type DashParsedVideoTrack = DashTrackCommon & {
|
|
|
171
158
|
width?: number;
|
|
172
159
|
height?: number;
|
|
173
160
|
frameRate?: number;
|
|
161
|
+
colorSpace?: VideoColorSpaceInit;
|
|
174
162
|
dynamicRange?: VideoDynamicRange;
|
|
175
163
|
};
|
|
176
164
|
type DashParsedAudioTrack = DashTrackCommon & {
|
|
@@ -412,7 +400,7 @@ declare class DashInputVideoTrackBacking extends DashTrackBackingBase {
|
|
|
412
400
|
internalTrack: InternalVideoTrack;
|
|
413
401
|
constructor(internalTrack: InternalVideoTrack);
|
|
414
402
|
getType(): "video";
|
|
415
|
-
getCodec(): VideoCodec | null;
|
|
403
|
+
getCodec(): VideoCodec$1 | null;
|
|
416
404
|
getCodedWidth(): number | Promise<any>;
|
|
417
405
|
getCodedHeight(): number | Promise<any>;
|
|
418
406
|
getSquarePixelWidth(): number | Promise<any>;
|
|
@@ -427,7 +415,7 @@ declare class DashInputAudioTrackBacking extends DashTrackBackingBase {
|
|
|
427
415
|
internalTrack: InternalAudioTrack;
|
|
428
416
|
constructor(internalTrack: InternalAudioTrack);
|
|
429
417
|
getType(): "audio";
|
|
430
|
-
getCodec(): AudioCodec | null;
|
|
418
|
+
getCodec(): AudioCodec$1 | null;
|
|
431
419
|
getNumberOfChannels(): number | Promise<any>;
|
|
432
420
|
getSampleRate(): number | Promise<any>;
|
|
433
421
|
}
|
|
@@ -475,6 +463,15 @@ declare class HlsSubtitlePlaylist implements HlsSegmentedInput {
|
|
|
475
463
|
getLiveRefreshInterval(): Promise<number | null>;
|
|
476
464
|
isRelativeToUnixEpoch(): Promise<boolean>;
|
|
477
465
|
}
|
|
466
|
+
declare class ExternalSubtitleSegmentedInput implements HlsSegmentedInput {
|
|
467
|
+
#private;
|
|
468
|
+
segments: HlsSegment[];
|
|
469
|
+
constructor(source: SourceWithRootPath, rootPath: string);
|
|
470
|
+
runUpdateSegments(): Promise<void>;
|
|
471
|
+
getDurationFromMetadata(options: DurationMetadataRequestOptions): Promise<number | null>;
|
|
472
|
+
getLiveRefreshInterval(): Promise<number | null>;
|
|
473
|
+
isRelativeToUnixEpoch(): Promise<boolean>;
|
|
474
|
+
}
|
|
478
475
|
declare class HlsSubtitleTrackBacking {
|
|
479
476
|
#private;
|
|
480
477
|
constructor(params: {
|
|
@@ -509,10 +506,49 @@ declare class HlsSubtitleTrackBacking {
|
|
|
509
506
|
getNextKeyPacket(_packet: EncodedPacket, _options: unknown): Promise<EncodedPacket | null>;
|
|
510
507
|
getSegmentedInput(): HlsSubtitlePlaylist;
|
|
511
508
|
}
|
|
509
|
+
declare class ExternalSubtitleTrackBacking {
|
|
510
|
+
#private;
|
|
511
|
+
constructor(params: {
|
|
512
|
+
id: number;
|
|
513
|
+
number: number;
|
|
514
|
+
pairingMask: bigint;
|
|
515
|
+
source: SourceWithRootPath;
|
|
516
|
+
codec?: SubtitleCodec | null;
|
|
517
|
+
codecString?: string | null;
|
|
518
|
+
languageCode?: string;
|
|
519
|
+
name?: string | null;
|
|
520
|
+
disposition?: Partial<TrackDisposition>;
|
|
521
|
+
});
|
|
522
|
+
getType(): "subtitle";
|
|
523
|
+
getId(): number;
|
|
524
|
+
getNumber(): number;
|
|
525
|
+
getCodec(): Promise<never>;
|
|
526
|
+
getInternalCodecId(): null;
|
|
527
|
+
getName(): string | null;
|
|
528
|
+
getLanguageCode(): string;
|
|
529
|
+
getTimeResolution(): number;
|
|
530
|
+
isRelativeToUnixEpoch(): Promise<boolean>;
|
|
531
|
+
getDisposition(): TrackDisposition;
|
|
532
|
+
getPairingMask(): bigint;
|
|
533
|
+
getBitrate(): null;
|
|
534
|
+
getAverageBitrate(): null;
|
|
535
|
+
getDurationFromMetadata(options: DurationMetadataRequestOptions): Promise<number | null>;
|
|
536
|
+
getLiveRefreshInterval(): Promise<number | null>;
|
|
537
|
+
getHasOnlyKeyPackets(): boolean;
|
|
538
|
+
getDecoderConfig(): Promise<null>;
|
|
539
|
+
getMetadataCodecParameterString(): Promise<string | null>;
|
|
540
|
+
getFirstPacket(_options: unknown): Promise<EncodedPacket | null>;
|
|
541
|
+
getPacket(_timestamp: number, _options: unknown): Promise<EncodedPacket | null>;
|
|
542
|
+
getNextPacket(_packet: EncodedPacket, _options: unknown): Promise<EncodedPacket | null>;
|
|
543
|
+
getKeyPacket(_timestamp: number, _options: unknown): Promise<EncodedPacket | null>;
|
|
544
|
+
getNextKeyPacket(_packet: EncodedPacket, _options: unknown): Promise<EncodedPacket | null>;
|
|
545
|
+
getSegmentedInput(): ExternalSubtitleSegmentedInput;
|
|
546
|
+
}
|
|
512
547
|
//#endregion
|
|
513
548
|
//#region src/mediabunny-input.d.ts
|
|
514
549
|
declare module 'mediabunny' {
|
|
515
550
|
interface Input<S extends Source = Source> {
|
|
551
|
+
_getDemuxer(): Promise<unknown>;
|
|
516
552
|
_getTrackBackings(): Promise<unknown[]>;
|
|
517
553
|
_wrapBackingAsTrack(backing: unknown): InputTrack$2;
|
|
518
554
|
}
|
|
@@ -521,10 +557,25 @@ type SegmentAccessMethods = {
|
|
|
521
557
|
getSegmentedInput(): HlsSegmentedInput | DashSegmentedInput;
|
|
522
558
|
getSegments(): Promise<(HlsSegment | DashSegment)[]>;
|
|
523
559
|
};
|
|
524
|
-
type
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
type
|
|
560
|
+
type VideoDynamicRangeMethods = {
|
|
561
|
+
getDynamicRange(): Promise<VideoDynamicRange>;
|
|
562
|
+
};
|
|
563
|
+
type MediabunnySubtitleTrackLike = InputTrack$2 & {
|
|
564
|
+
type: 'subtitle';
|
|
565
|
+
};
|
|
566
|
+
type InputTrack$1 = InputTrack$2 & SegmentAccessMethods;
|
|
567
|
+
type InputVideoTrack = InputVideoTrack$1 & SegmentAccessMethods & VideoDynamicRangeMethods;
|
|
568
|
+
type InputAudioTrack = InputAudioTrack$1 & SegmentAccessMethods;
|
|
569
|
+
type InputSubtitleTrack = MediabunnySubtitleTrackLike & SegmentAccessMethods;
|
|
570
|
+
type InputSubtitleSource = PathedSource | SourceRef<PathedSource>;
|
|
571
|
+
type InputSubtitleTrackMetadata = {
|
|
572
|
+
codec?: SubtitleCodec | null;
|
|
573
|
+
codecString?: string | null;
|
|
574
|
+
disposition?: Partial<TrackDisposition>;
|
|
575
|
+
languageCode?: string;
|
|
576
|
+
name?: string | null;
|
|
577
|
+
pairWith?: InputVideoTrack | Iterable<InputVideoTrack>;
|
|
578
|
+
};
|
|
528
579
|
type SegmentableBacking = {
|
|
529
580
|
getId(): number;
|
|
530
581
|
getNumber(): number;
|
|
@@ -545,6 +596,7 @@ type SegmentableBacking = {
|
|
|
545
596
|
getMetadataCodecParameterString?(): string | null | Promise<string | null>;
|
|
546
597
|
getSegmentedInput?(): HlsSegmentedInput | DashSegmentedInput;
|
|
547
598
|
};
|
|
599
|
+
type NativeTrackBacking = SegmentableBacking;
|
|
548
600
|
type TrackBacking = NativeTrackBacking | SegmentableBacking;
|
|
549
601
|
declare const BACKING_TYPE_SUBTITLE = "subtitle";
|
|
550
602
|
declare const BACKING_TYPE_AUDIO = "audio";
|
|
@@ -552,13 +604,15 @@ declare const BACKING_TYPE_VIDEO = "video";
|
|
|
552
604
|
declare const preserveSubtitleBackingsOnInput: (input: Input$1) => Input$1<Source>;
|
|
553
605
|
declare class SegmentedMediabunnyInput<S extends Source = Source> extends Input$1<S> {
|
|
554
606
|
#private;
|
|
555
|
-
_wrapBackingAsTrack(backing: TrackBacking):
|
|
556
|
-
_getSyntheticTrackBackings(type?: typeof BACKING_TYPE_VIDEO | typeof BACKING_TYPE_AUDIO | typeof BACKING_TYPE_SUBTITLE): Promise<HlsSubtitleTrackBacking[]>;
|
|
557
|
-
getTracks(query?: InputTrackQuery$1<
|
|
558
|
-
getVideoTracks(query?: InputTrackQuery$1<
|
|
559
|
-
getAudioTracks(query?: InputTrackQuery$1<
|
|
560
|
-
|
|
561
|
-
|
|
607
|
+
_wrapBackingAsTrack(backing: TrackBacking): InputTrack$1;
|
|
608
|
+
_getSyntheticTrackBackings(type?: typeof BACKING_TYPE_VIDEO | typeof BACKING_TYPE_AUDIO | typeof BACKING_TYPE_SUBTITLE): Promise<(ExternalSubtitleTrackBacking | HlsSubtitleTrackBacking)[]>;
|
|
609
|
+
getTracks(query?: InputTrackQuery$1<InputTrack$1>): Promise<InputTrack$1[]>;
|
|
610
|
+
getVideoTracks(query?: InputTrackQuery$1<InputVideoTrack>): Promise<InputVideoTrack[]>;
|
|
611
|
+
getAudioTracks(query?: InputTrackQuery$1<InputAudioTrack>): Promise<InputAudioTrack[]>;
|
|
612
|
+
getSubtitleTracks(query?: InputTrackQuery$1<InputSubtitleTrack>): Promise<InputSubtitleTrack[]>;
|
|
613
|
+
getPrimaryVideoTrack(query?: InputTrackQuery$1<InputVideoTrack>): Promise<InputVideoTrack | null>;
|
|
614
|
+
getPrimaryAudioTrack(query?: InputTrackQuery$1<InputAudioTrack>): Promise<InputAudioTrack | null>;
|
|
615
|
+
addSubtitleTrack(source: InputSubtitleSource, metadata?: InputSubtitleTrackMetadata): InputSubtitleTrack;
|
|
562
616
|
}
|
|
563
617
|
//#endregion
|
|
564
618
|
//#region src/index.d.ts
|
|
@@ -567,14 +621,12 @@ type DashaInputOptions<S extends Source = Source> = Omit<InputOptions<S>, 'forma
|
|
|
567
621
|
};
|
|
568
622
|
type InputSegment = HlsSegment | DashSegment;
|
|
569
623
|
type InputSegmentedInput = HlsSegmentedInput | DashSegmentedInput;
|
|
570
|
-
type InputTrack$1 = MediabunnyTrackWithSegments;
|
|
571
|
-
type InputVideoTrack = MediabunnyVideoTrackWithSegments;
|
|
572
|
-
type InputAudioTrack = MediabunnyAudioTrackWithSegments;
|
|
573
624
|
declare class Input<S extends Source = Source> extends SegmentedMediabunnyInput<S> {
|
|
574
625
|
constructor(options: DashaInputOptions<S>);
|
|
575
626
|
}
|
|
576
627
|
declare const isInput: (value: unknown) => value is Input;
|
|
577
628
|
declare const getSegmentedInput: (track: InputTrack$1) => InputSegmentedInput;
|
|
578
629
|
declare const getSegments: (track: InputTrack$1) => Promise<InputSegment[]>;
|
|
630
|
+
declare const ALL_FORMATS: InputFormat[];
|
|
579
631
|
//#endregion
|
|
580
|
-
export { DASH, DASH_FORMATS, type DashSegment, type DashSegmentedInput, FilePathSource, HLS_FORMATS, type HlsSegment, type HlsSegmentedInput, Input, InputAudioTrack, InputSegment, InputSegmentedInput, InputTrack$1 as InputTrack, type InputTrackQuery, type InputTrackWithBacking, InputVideoTrack, type MediaCodec, type SubtitleCodec, UrlSource, asc, desc, getSegmentedInput, getSegments, isInput, prefer, preserveSubtitleBackingsOnInput };
|
|
632
|
+
export { ALL_FORMATS, type AudioCodec, DASH, DASH_FORMATS, type DashSegment, type DashSegmentedInput, FilePathSource, HLS, HLS_FORMATS, type HlsSegment, type HlsSegmentedInput, Input, type InputAudioTrack, InputSegment, InputSegmentedInput, type InputSubtitleSource, type InputSubtitleTrack, type InputSubtitleTrackMetadata, type InputTrack$1 as InputTrack, type InputTrackQuery, type InputTrackWithBacking, type InputVideoTrack, MP3, MP4, type MaybePromise, type MediaCodec, type SubtitleCodec, UrlSource, type VideoCodec, type VideoDynamicRange, asc, desc, getSegmentedInput, getSegments, isInput, prefer, preserveSubtitleBackingsOnInput };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ADTS, CustomPathedSource, FilePathSource, HLS, HLS_FORMATS, Input as Input$1, InputFormat, InputTrack, MATROSKA, MP3, MP4, QTFF, UrlSource, WEBM, asc, desc, prefer } from "mediabunny";
|
|
1
|
+
import { ADTS, ALL_FORMATS as ALL_FORMATS$1, CustomPathedSource, FilePathSource, HLS, HLS as HLS$1, HLS_FORMATS, Input as Input$1, InputFormat, InputTrack, InputVideoTrack, MATROSKA, MP3, MP3 as MP3$1, MP4, MP4 as MP4$1, QTFF, SourceRef, UrlSource, WEBM, asc, desc, prefer } from "mediabunny";
|
|
2
2
|
import { DOMParser } from "@xmldom/xmldom";
|
|
3
3
|
import { Temporal } from "temporal-polyfill";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
@@ -158,6 +158,32 @@ const MATRIX = {
|
|
|
158
158
|
YCbCr_BT_2020_and_2100: 9,
|
|
159
159
|
ICtCp_BT_2100: 14
|
|
160
160
|
};
|
|
161
|
+
const DOLBY_VISION_CODECS = [
|
|
162
|
+
"dva1",
|
|
163
|
+
"dvav",
|
|
164
|
+
"dvhe",
|
|
165
|
+
"dvh1"
|
|
166
|
+
];
|
|
167
|
+
const COLOR_PRIMARIES_MAP = new Map([
|
|
168
|
+
[PRIMARIES.BT_709, "bt709"],
|
|
169
|
+
[PRIMARIES.BT_601_625, "bt470bg"],
|
|
170
|
+
[PRIMARIES.BT_601_525, "smpte170m"],
|
|
171
|
+
[PRIMARIES.BT_2020_and_2100, "bt2020"],
|
|
172
|
+
[PRIMARIES.SMPTE_ST_2113_and_EG_4321, "smpte432"]
|
|
173
|
+
]);
|
|
174
|
+
const TRANSFER_CHARACTERISTICS_MAP = new Map([
|
|
175
|
+
[TRANSFER.BT_709, "bt709"],
|
|
176
|
+
[TRANSFER.BT_601, "smpte170m"],
|
|
177
|
+
[TRANSFER.BT_2100_PQ, "pq"],
|
|
178
|
+
[TRANSFER.BT_2100_HLG, "hlg"]
|
|
179
|
+
]);
|
|
180
|
+
const MATRIX_COEFFICIENTS_MAP = new Map([
|
|
181
|
+
[MATRIX.RGB, "rgb"],
|
|
182
|
+
[MATRIX.YCbCr_BT_709, "bt709"],
|
|
183
|
+
[MATRIX.YCbCr_BT_601_625, "bt470bg"],
|
|
184
|
+
[MATRIX.YCbCr_BT_601_525, "smpte170m"],
|
|
185
|
+
[MATRIX.YCbCr_BT_2020_and_2100, "bt2020-ncl"]
|
|
186
|
+
]);
|
|
161
187
|
const parseVideoCodecFromMime = (mime) => {
|
|
162
188
|
const target = mime.toLowerCase().trim().split(".")[0];
|
|
163
189
|
const avc = [
|
|
@@ -199,6 +225,52 @@ const parseDynamicRangeFromCicp = (primaries, transfer, matrix) => {
|
|
|
199
225
|
else if (TRANSFER.BT_2100_HLG === transfer) return "hlg";
|
|
200
226
|
else return "sdr";
|
|
201
227
|
};
|
|
228
|
+
const parseColorSpaceFromCicp = (primaries, transfer, matrix) => {
|
|
229
|
+
const normalizedTransfer = transfer == 5 ? TRANSFER.BT_601 : transfer;
|
|
230
|
+
return {
|
|
231
|
+
primaries: COLOR_PRIMARIES_MAP.get(primaries),
|
|
232
|
+
transfer: TRANSFER_CHARACTERISTICS_MAP.get(normalizedTransfer),
|
|
233
|
+
matrix: MATRIX_COEFFICIENTS_MAP.get(matrix)
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
const parseDynamicRangeFromCodecString = (codecs) => {
|
|
237
|
+
const normalized = codecs?.trim().toLowerCase();
|
|
238
|
+
if (!normalized) return;
|
|
239
|
+
return DOLBY_VISION_CODECS.some((value) => normalized.startsWith(value)) ? "dv" : void 0;
|
|
240
|
+
};
|
|
241
|
+
const getCicpValues = (supplementalProps = [], essentialProps = []) => {
|
|
242
|
+
const primariesScheme = "urn:mpeg:mpegB:cicp:ColourPrimaries";
|
|
243
|
+
const transferScheme = "urn:mpeg:mpegB:cicp:TransferCharacteristics";
|
|
244
|
+
const matrixScheme = "urn:mpeg:mpegB:cicp:MatrixCoefficients";
|
|
245
|
+
const allProps = [...essentialProps, ...supplementalProps];
|
|
246
|
+
const getValues = (schemeIdUri) => allProps.filter((prop) => prop.schemeIdUri === schemeIdUri).flatMap((prop) => prop.value ? [Number.parseInt(prop.value, 10)] : []);
|
|
247
|
+
return {
|
|
248
|
+
primaries: getValues(primariesScheme)[0] ?? 0,
|
|
249
|
+
transfer: getValues(transferScheme)[0] ?? 0,
|
|
250
|
+
matrix: getValues(matrixScheme)[0] ?? 0
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
const parseColorSpace = (supplementalProps = [], essentialProps = []) => {
|
|
254
|
+
const { primaries, transfer, matrix } = getCicpValues(supplementalProps, essentialProps);
|
|
255
|
+
return parseColorSpaceFromCicp(primaries, transfer, matrix);
|
|
256
|
+
};
|
|
257
|
+
const parseDynamicRangeFromHlsVideoRange = (videoRange, codecs) => {
|
|
258
|
+
const normalized = videoRange?.replaceAll("\"", "").trim().toUpperCase();
|
|
259
|
+
if (!normalized) return;
|
|
260
|
+
const codecDynamicRange = parseDynamicRangeFromCodecString(codecs);
|
|
261
|
+
if (normalized === "PQ") return codecDynamicRange ?? "hdr10";
|
|
262
|
+
if (normalized === "HLG") return "hlg";
|
|
263
|
+
if (normalized === "SDR") return "sdr";
|
|
264
|
+
};
|
|
265
|
+
const parseDynamicRangeFromColorSpace = (colorSpace) => {
|
|
266
|
+
if (!colorSpace) return;
|
|
267
|
+
if (colorSpace.transfer === "pq") return "hdr10";
|
|
268
|
+
if (colorSpace.transfer === "hlg") return "hlg";
|
|
269
|
+
if (colorSpace.primaries != null || colorSpace.transfer != null || colorSpace.matrix != null || colorSpace.fullRange != null) return "sdr";
|
|
270
|
+
};
|
|
271
|
+
const inferDynamicRange = (params) => {
|
|
272
|
+
return parseDynamicRangeFromCodecString(params.codecs) ?? parseDynamicRangeFromHlsVideoRange(params.videoRange, params.codecs) ?? parseDynamicRangeFromColorSpace(params.colorSpace) ?? "sdr";
|
|
273
|
+
};
|
|
202
274
|
const parseVideoCodec = (codecs) => {
|
|
203
275
|
for (const codec of codecs.toLowerCase().split(",")) {
|
|
204
276
|
const mime = codec.trim().split(".")[0];
|
|
@@ -218,18 +290,10 @@ const tryParseVideoCodec = (codecs) => {
|
|
|
218
290
|
}
|
|
219
291
|
};
|
|
220
292
|
const parseDynamicRange = (codecs, supplementalProps = [], essentialProps = []) => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"dvh1"
|
|
226
|
-
].some((value) => codecs.startsWith(value))) return "dv";
|
|
227
|
-
const primariesScheme = "urn:mpeg:mpegB:cicp:ColourPrimaries";
|
|
228
|
-
const transferScheme = "urn:mpeg:mpegB:cicp:TransferCharacteristics";
|
|
229
|
-
const matrixScheme = "urn:mpeg:mpegB:cicp:MatrixCoefficients";
|
|
230
|
-
const allProps = [...essentialProps, ...supplementalProps];
|
|
231
|
-
const getValues = (schemeIdUri) => allProps.filter((prop) => prop.schemeIdUri === schemeIdUri).flatMap((prop) => prop.value ? [parseInt(prop.value)] : []);
|
|
232
|
-
return parseDynamicRangeFromCicp(getValues(primariesScheme).reduce((acc, current) => acc + current, 0), getValues(transferScheme).reduce((acc, current) => acc + current, 0), getValues(matrixScheme).reduce((acc, current) => acc + current, 0));
|
|
293
|
+
const codecDynamicRange = parseDynamicRangeFromCodecString(codecs);
|
|
294
|
+
if (codecDynamicRange) return codecDynamicRange;
|
|
295
|
+
const { primaries, transfer, matrix } = getCicpValues(supplementalProps, essentialProps);
|
|
296
|
+
return parseDynamicRangeFromCicp(primaries, transfer, matrix);
|
|
233
297
|
};
|
|
234
298
|
//#endregion
|
|
235
299
|
//#region src/dash/dash-misc.ts
|
|
@@ -838,6 +902,74 @@ var HlsSubtitlePlaylist = class {
|
|
|
838
902
|
this.#streamHasEnded = streamHasEnded;
|
|
839
903
|
}
|
|
840
904
|
};
|
|
905
|
+
var ExternalSubtitleSegmentedInput = class {
|
|
906
|
+
segments = [];
|
|
907
|
+
#source;
|
|
908
|
+
#rootPath;
|
|
909
|
+
#delegate = null;
|
|
910
|
+
#loadPromise = null;
|
|
911
|
+
constructor(source, rootPath) {
|
|
912
|
+
this.#source = source;
|
|
913
|
+
this.#rootPath = rootPath;
|
|
914
|
+
}
|
|
915
|
+
runUpdateSegments() {
|
|
916
|
+
return this.#loadPromise ??= (async () => {
|
|
917
|
+
if (this.#delegate) {
|
|
918
|
+
await this.#delegate.runUpdateSegments();
|
|
919
|
+
this.segments = this.#delegate.segments;
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const loaded = await loadPlaylistText(this.#source, this.#rootPath);
|
|
923
|
+
if (splitPlaylistLines(loaded.text)[0] === "#EXTM3U") {
|
|
924
|
+
this.#delegate = new HlsSubtitlePlaylist(this.#source, loaded.path);
|
|
925
|
+
await this.#delegate.runUpdateSegments();
|
|
926
|
+
this.segments = this.#delegate.segments;
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const segment = {
|
|
930
|
+
timestamp: 0,
|
|
931
|
+
duration: 0,
|
|
932
|
+
relativeToUnixEpoch: false,
|
|
933
|
+
firstSegment: null,
|
|
934
|
+
sequenceNumber: 0,
|
|
935
|
+
location: {
|
|
936
|
+
path: toSegmentPath(loaded.path),
|
|
937
|
+
offset: 0,
|
|
938
|
+
length: null
|
|
939
|
+
},
|
|
940
|
+
encryption: null,
|
|
941
|
+
initSegment: null,
|
|
942
|
+
lastProgramDateTimeSeconds: null
|
|
943
|
+
};
|
|
944
|
+
segment.firstSegment = segment;
|
|
945
|
+
this.segments = [segment];
|
|
946
|
+
this.#delegate = {
|
|
947
|
+
segments: this.segments,
|
|
948
|
+
runUpdateSegments: async () => {},
|
|
949
|
+
getDurationFromMetadata: async (_options) => null,
|
|
950
|
+
getLiveRefreshInterval: async () => null,
|
|
951
|
+
isRelativeToUnixEpoch: async () => false
|
|
952
|
+
};
|
|
953
|
+
})().finally(() => {
|
|
954
|
+
this.#loadPromise = null;
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
async getDurationFromMetadata(options) {
|
|
958
|
+
await this.runUpdateSegments();
|
|
959
|
+
if (this.#delegate) return this.#delegate.getDurationFromMetadata(options);
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
async getLiveRefreshInterval() {
|
|
963
|
+
await this.runUpdateSegments();
|
|
964
|
+
if (this.#delegate) return this.#delegate.getLiveRefreshInterval();
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
async isRelativeToUnixEpoch() {
|
|
968
|
+
await this.runUpdateSegments();
|
|
969
|
+
if (this.#delegate) return this.#delegate.isRelativeToUnixEpoch();
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
};
|
|
841
973
|
var HlsSubtitleTrackBacking = class {
|
|
842
974
|
#id;
|
|
843
975
|
#number;
|
|
@@ -930,6 +1062,118 @@ var HlsSubtitleTrackBacking = class {
|
|
|
930
1062
|
return this.#segmentedInput;
|
|
931
1063
|
}
|
|
932
1064
|
};
|
|
1065
|
+
var ExternalSubtitleTrackBacking = class {
|
|
1066
|
+
#id;
|
|
1067
|
+
#number;
|
|
1068
|
+
#pairingMask;
|
|
1069
|
+
#source;
|
|
1070
|
+
#segmentedInput;
|
|
1071
|
+
#languageCode;
|
|
1072
|
+
#name;
|
|
1073
|
+
#disposition;
|
|
1074
|
+
#codec;
|
|
1075
|
+
#codecString;
|
|
1076
|
+
#codecInfoPromise = null;
|
|
1077
|
+
constructor(params) {
|
|
1078
|
+
this.#id = params.id;
|
|
1079
|
+
this.#number = params.number;
|
|
1080
|
+
this.#pairingMask = params.pairingMask;
|
|
1081
|
+
this.#source = params.source;
|
|
1082
|
+
this.#segmentedInput = new ExternalSubtitleSegmentedInput(params.source, params.source.rootPath);
|
|
1083
|
+
this.#languageCode = params.languageCode ?? "und";
|
|
1084
|
+
this.#name = params.name ?? null;
|
|
1085
|
+
this.#codec = params.codec ?? tryParseSubtitleCodec(params.codecString ?? "") ?? void 0;
|
|
1086
|
+
this.#codecString = params.codecString ?? params.codec ?? void 0;
|
|
1087
|
+
this.#disposition = {
|
|
1088
|
+
...DEFAULT_TRACK_DISPOSITION$1,
|
|
1089
|
+
...params.disposition
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
getType() {
|
|
1093
|
+
return "subtitle";
|
|
1094
|
+
}
|
|
1095
|
+
getId() {
|
|
1096
|
+
return this.#id;
|
|
1097
|
+
}
|
|
1098
|
+
getNumber() {
|
|
1099
|
+
return this.#number;
|
|
1100
|
+
}
|
|
1101
|
+
async #getCodecInfo() {
|
|
1102
|
+
if (this.#codec !== void 0 && this.#codecString !== void 0) return {
|
|
1103
|
+
codec: this.#codec,
|
|
1104
|
+
codecString: this.#codecString
|
|
1105
|
+
};
|
|
1106
|
+
const info = await (this.#codecInfoPromise ??= detectSubtitleCodecFromUri(this.#source, this.#source.rootPath));
|
|
1107
|
+
this.#codec = info.codec;
|
|
1108
|
+
this.#codecString = info.codecString;
|
|
1109
|
+
return info;
|
|
1110
|
+
}
|
|
1111
|
+
getCodec() {
|
|
1112
|
+
if (this.#codec !== void 0) return this.#codec;
|
|
1113
|
+
return this.#getCodecInfo().then((info) => info.codec);
|
|
1114
|
+
}
|
|
1115
|
+
getInternalCodecId() {
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
getName() {
|
|
1119
|
+
return this.#name;
|
|
1120
|
+
}
|
|
1121
|
+
getLanguageCode() {
|
|
1122
|
+
return this.#languageCode;
|
|
1123
|
+
}
|
|
1124
|
+
getTimeResolution() {
|
|
1125
|
+
return 1e3;
|
|
1126
|
+
}
|
|
1127
|
+
isRelativeToUnixEpoch() {
|
|
1128
|
+
return this.#segmentedInput.isRelativeToUnixEpoch();
|
|
1129
|
+
}
|
|
1130
|
+
getDisposition() {
|
|
1131
|
+
return this.#disposition;
|
|
1132
|
+
}
|
|
1133
|
+
getPairingMask() {
|
|
1134
|
+
return this.#pairingMask;
|
|
1135
|
+
}
|
|
1136
|
+
getBitrate() {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
getAverageBitrate() {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
getDurationFromMetadata(options) {
|
|
1143
|
+
return this.#segmentedInput.getDurationFromMetadata(options);
|
|
1144
|
+
}
|
|
1145
|
+
getLiveRefreshInterval() {
|
|
1146
|
+
return this.#segmentedInput.getLiveRefreshInterval();
|
|
1147
|
+
}
|
|
1148
|
+
getHasOnlyKeyPackets() {
|
|
1149
|
+
return true;
|
|
1150
|
+
}
|
|
1151
|
+
async getDecoderConfig() {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
async getMetadataCodecParameterString() {
|
|
1155
|
+
if (this.#codecString !== void 0) return this.#codecString;
|
|
1156
|
+
return this.#getCodecInfo().then((info) => info.codecString);
|
|
1157
|
+
}
|
|
1158
|
+
async getFirstPacket(_options) {
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
async getPacket(_timestamp, _options) {
|
|
1162
|
+
return null;
|
|
1163
|
+
}
|
|
1164
|
+
async getNextPacket(_packet, _options) {
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1167
|
+
async getKeyPacket(_timestamp, _options) {
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
async getNextKeyPacket(_packet, _options) {
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
getSegmentedInput() {
|
|
1174
|
+
return this.#segmentedInput;
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
933
1177
|
const parseMasterPlaylistSubtitles = async (input) => {
|
|
934
1178
|
const source = input.source;
|
|
935
1179
|
if (!("rootPath" in source) || typeof source.rootPath !== "string") return [];
|
|
@@ -992,6 +1236,116 @@ const getHlsSubtitleTrackBackings = (input) => {
|
|
|
992
1236
|
};
|
|
993
1237
|
//#endregion
|
|
994
1238
|
//#region src/mediabunny-input.ts
|
|
1239
|
+
const CUSTOM_SUBTITLE_TRACK_ID_OFFSET = 1e9;
|
|
1240
|
+
const CUSTOM_PAIRING_BIT_START = 1024n;
|
|
1241
|
+
const EXTRA_PAIRING_MASK = Symbol.for("dasha.extra-pairing-mask");
|
|
1242
|
+
const ORIGINAL_GET_PAIRING_MASK = Symbol.for("dasha.original-get-pairing-mask");
|
|
1243
|
+
const HLS_VARIANT_INF_LINE = "#EXT-X-STREAM-INF:";
|
|
1244
|
+
const HLS_DEMUXER_PATCHED = Symbol.for("dasha.hls-demuxer-patched");
|
|
1245
|
+
const HLS_DEMUXER_METADATA_PATCH = Symbol.for("dasha.hls-demuxer-metadata-patch");
|
|
1246
|
+
const HLS_VIDEO_RANGE_APPLIED = Symbol.for("dasha.hls-video-range-applied");
|
|
1247
|
+
var HlsAttributeList = class {
|
|
1248
|
+
#attributes = {};
|
|
1249
|
+
constructor(str) {
|
|
1250
|
+
let key = "";
|
|
1251
|
+
let value = "";
|
|
1252
|
+
let inValue = false;
|
|
1253
|
+
let inQuotes = false;
|
|
1254
|
+
for (const char of str) if (char === "\"") inQuotes = !inQuotes;
|
|
1255
|
+
else if (char === "=" && !inValue && !inQuotes) inValue = true;
|
|
1256
|
+
else if (char === "," && !inQuotes) {
|
|
1257
|
+
if (key) this.#attributes[key.trim().toLowerCase()] = value;
|
|
1258
|
+
key = "";
|
|
1259
|
+
value = "";
|
|
1260
|
+
inValue = false;
|
|
1261
|
+
} else if (inValue) value += char;
|
|
1262
|
+
else key += char;
|
|
1263
|
+
if (key) this.#attributes[key.trim().toLowerCase()] = value;
|
|
1264
|
+
}
|
|
1265
|
+
get(name) {
|
|
1266
|
+
return this.#attributes[name.toLowerCase()] ?? null;
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
const extractHlsVideoRanges = (text) => {
|
|
1270
|
+
const lines = text.split(/\r?\n/);
|
|
1271
|
+
const videoRanges = [];
|
|
1272
|
+
for (const rawLine of lines) {
|
|
1273
|
+
const line = rawLine.trim();
|
|
1274
|
+
if (!line.startsWith(HLS_VARIANT_INF_LINE)) continue;
|
|
1275
|
+
const attributes = new HlsAttributeList(line.slice(18));
|
|
1276
|
+
videoRanges.push(attributes.get("video-range"));
|
|
1277
|
+
}
|
|
1278
|
+
return videoRanges;
|
|
1279
|
+
};
|
|
1280
|
+
const getPairingMaskIndexes = (pairingMask) => {
|
|
1281
|
+
const indexes = [];
|
|
1282
|
+
let value = pairingMask;
|
|
1283
|
+
let index = 0;
|
|
1284
|
+
while (value > 0n) {
|
|
1285
|
+
if ((value & 1n) === 1n) indexes.push(index);
|
|
1286
|
+
value >>= 1n;
|
|
1287
|
+
index++;
|
|
1288
|
+
}
|
|
1289
|
+
return indexes;
|
|
1290
|
+
};
|
|
1291
|
+
const applyHlsVideoRangeMetadata = async (demuxer) => {
|
|
1292
|
+
if (demuxer[HLS_VIDEO_RANGE_APPLIED] || !demuxer.hasMasterPlaylist || !demuxer.internalTracks?.length) {
|
|
1293
|
+
demuxer[HLS_VIDEO_RANGE_APPLIED] = true;
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
const slice = await demuxer.input?._reader?.requestEntireFile();
|
|
1297
|
+
if (!slice) {
|
|
1298
|
+
demuxer[HLS_VIDEO_RANGE_APPLIED] = true;
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const bytes = slice instanceof Uint8Array ? slice : "bytes" in slice ? slice.bytes : slice.data;
|
|
1302
|
+
const videoRanges = extractHlsVideoRanges(new TextDecoder().decode(bytes));
|
|
1303
|
+
if (!videoRanges.length) {
|
|
1304
|
+
demuxer[HLS_VIDEO_RANGE_APPLIED] = true;
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
for (const track of demuxer.internalTracks) {
|
|
1308
|
+
if (track.info.type !== "video") continue;
|
|
1309
|
+
const matchedRanges = getPairingMaskIndexes(track.pairingMask).map((index) => videoRanges[index] ?? null).filter((value) => value != null);
|
|
1310
|
+
const uniqueRanges = [...new Set(matchedRanges)];
|
|
1311
|
+
track.videoRange = uniqueRanges.length === 1 ? uniqueRanges[0] : null;
|
|
1312
|
+
}
|
|
1313
|
+
demuxer[HLS_VIDEO_RANGE_APPLIED] = true;
|
|
1314
|
+
};
|
|
1315
|
+
const patchHlsDemuxer = (demuxer) => {
|
|
1316
|
+
if (!(demuxer instanceof Object) || !("readMetadata" in demuxer) || typeof demuxer.readMetadata !== "function" || !("input" in demuxer) || demuxer[HLS_DEMUXER_PATCHED]) return demuxer;
|
|
1317
|
+
const candidate = demuxer;
|
|
1318
|
+
if (candidate.constructor?.name !== "HlsDemuxer") return demuxer;
|
|
1319
|
+
const originalReadMetadata = candidate.readMetadata.bind(candidate);
|
|
1320
|
+
candidate.readMetadata = () => {
|
|
1321
|
+
if (candidate[HLS_DEMUXER_METADATA_PATCH]) return candidate[HLS_DEMUXER_METADATA_PATCH];
|
|
1322
|
+
const promise = Promise.resolve(originalReadMetadata()).then(() => applyHlsVideoRangeMetadata(candidate));
|
|
1323
|
+
candidate[HLS_DEMUXER_METADATA_PATCH] = promise.catch((error) => {
|
|
1324
|
+
if (candidate[HLS_DEMUXER_METADATA_PATCH] === promise) candidate[HLS_DEMUXER_METADATA_PATCH] = void 0;
|
|
1325
|
+
throw error;
|
|
1326
|
+
});
|
|
1327
|
+
return candidate[HLS_DEMUXER_METADATA_PATCH];
|
|
1328
|
+
};
|
|
1329
|
+
candidate[HLS_DEMUXER_PATCHED] = true;
|
|
1330
|
+
return demuxer;
|
|
1331
|
+
};
|
|
1332
|
+
const getDynamicRangeForTrack = async (track) => {
|
|
1333
|
+
const backing = track._backing;
|
|
1334
|
+
const manifestDynamicRange = backing?.internalTrack?.track?.dynamicRange;
|
|
1335
|
+
if (manifestDynamicRange) return manifestDynamicRange;
|
|
1336
|
+
const codecString = await track.getCodecParameterString().catch(() => null);
|
|
1337
|
+
const videoRange = backing?.internalTrack?.videoRange ?? null;
|
|
1338
|
+
const fromMetadata = inferDynamicRange({
|
|
1339
|
+
codecs: codecString,
|
|
1340
|
+
videoRange
|
|
1341
|
+
});
|
|
1342
|
+
if (fromMetadata !== "sdr" || videoRange != null) return fromMetadata;
|
|
1343
|
+
return inferDynamicRange({
|
|
1344
|
+
codecs: codecString,
|
|
1345
|
+
colorSpace: await track.getColorSpace().catch(() => null),
|
|
1346
|
+
videoRange
|
|
1347
|
+
});
|
|
1348
|
+
};
|
|
995
1349
|
const requireSync = (value, getterName, asyncName) => {
|
|
996
1350
|
if (value instanceof Promise) throw new Error(`'${getterName}' is not available synchronously for this track. Use '${asyncName}()' instead.`);
|
|
997
1351
|
return value;
|
|
@@ -1045,6 +1399,10 @@ const patchBaseMediabunnyInput = () => {
|
|
|
1045
1399
|
prototype.getAudioTracks = function(query) {
|
|
1046
1400
|
return getTrackBackingsByType(this, BACKING_TYPE_AUDIO).then((backings) => queryWrappedTracks(this, backings, query));
|
|
1047
1401
|
};
|
|
1402
|
+
const originalGetDemuxer = prototype._getDemuxer;
|
|
1403
|
+
prototype._getDemuxer = function() {
|
|
1404
|
+
return originalGetDemuxer.call(this).then((demuxer) => patchHlsDemuxer(demuxer));
|
|
1405
|
+
};
|
|
1048
1406
|
prototype[BASE_INPUT_PATCHED] = true;
|
|
1049
1407
|
};
|
|
1050
1408
|
const getSegmentedInputForTrack = (track) => {
|
|
@@ -1054,6 +1412,7 @@ const getSegmentedInputForTrack = (track) => {
|
|
|
1054
1412
|
return internalTrack.demuxer.getSegmentedInputForPath(internalTrack.fullPath);
|
|
1055
1413
|
};
|
|
1056
1414
|
const addSegmentAccess = (track) => new Proxy(track, { get(target, prop) {
|
|
1415
|
+
if (prop === "getDynamicRange" && target instanceof InputVideoTrack) return () => getDynamicRangeForTrack(target);
|
|
1057
1416
|
if (prop === "getSegmentedInput") return () => getSegmentedInputForTrack(target);
|
|
1058
1417
|
if (prop === "getSegments") return async () => {
|
|
1059
1418
|
const segmentedInput = getSegmentedInputForTrack(target);
|
|
@@ -1104,9 +1463,13 @@ var SegmentedMediabunnyInput = class extends Input$1 {
|
|
|
1104
1463
|
#trackCache = /* @__PURE__ */ new WeakMap();
|
|
1105
1464
|
#subtitleTrackCache = /* @__PURE__ */ new WeakMap();
|
|
1106
1465
|
#hlsSubtitleBackingsPromise = null;
|
|
1466
|
+
#customSubtitleBackings = [];
|
|
1467
|
+
#nextCustomSubtitleTrackId = CUSTOM_SUBTITLE_TRACK_ID_OFFSET;
|
|
1468
|
+
#nextCustomSubtitleTrackNumber = CUSTOM_SUBTITLE_TRACK_ID_OFFSET;
|
|
1469
|
+
#nextPairingBitIndex = null;
|
|
1107
1470
|
async #queryTracks(query, type) {
|
|
1108
|
-
const
|
|
1109
|
-
return queryWrappedTracks(
|
|
1471
|
+
const internalInput = this;
|
|
1472
|
+
return queryWrappedTracks(internalInput, await getTrackBackingsByType(internalInput, type), query);
|
|
1110
1473
|
}
|
|
1111
1474
|
_wrapBackingAsTrack(backing) {
|
|
1112
1475
|
const track = backing.getType?.() === "subtitle" ? this.#wrapSubtitleBacking(backing) : super._wrapBackingAsTrack(backing);
|
|
@@ -1118,7 +1481,8 @@ var SegmentedMediabunnyInput = class extends Input$1 {
|
|
|
1118
1481
|
}
|
|
1119
1482
|
async _getSyntheticTrackBackings(type) {
|
|
1120
1483
|
if (type && type !== BACKING_TYPE_SUBTITLE) return [];
|
|
1121
|
-
|
|
1484
|
+
const backings = [...this.#customSubtitleBackings];
|
|
1485
|
+
if (await this.getFormat() !== HLS$1) return backings;
|
|
1122
1486
|
if (!this.#hlsSubtitleBackingsPromise) {
|
|
1123
1487
|
const promise = getHlsSubtitleTrackBackings(this).catch((error) => {
|
|
1124
1488
|
if (this.#hlsSubtitleBackingsPromise === promise) this.#hlsSubtitleBackingsPromise = null;
|
|
@@ -1126,7 +1490,7 @@ var SegmentedMediabunnyInput = class extends Input$1 {
|
|
|
1126
1490
|
});
|
|
1127
1491
|
this.#hlsSubtitleBackingsPromise = promise;
|
|
1128
1492
|
}
|
|
1129
|
-
return this.#hlsSubtitleBackingsPromise;
|
|
1493
|
+
return [...backings, ...await this.#hlsSubtitleBackingsPromise];
|
|
1130
1494
|
}
|
|
1131
1495
|
#wrapSubtitleBacking(backing) {
|
|
1132
1496
|
const existing = this.#subtitleTrackCache.get(backing);
|
|
@@ -1144,12 +1508,83 @@ var SegmentedMediabunnyInput = class extends Input$1 {
|
|
|
1144
1508
|
async getAudioTracks(query) {
|
|
1145
1509
|
return await this.#queryTracks(query, BACKING_TYPE_AUDIO);
|
|
1146
1510
|
}
|
|
1511
|
+
async getSubtitleTracks(query) {
|
|
1512
|
+
return await this.#queryTracks(query, BACKING_TYPE_SUBTITLE);
|
|
1513
|
+
}
|
|
1147
1514
|
async getPrimaryVideoTrack(query) {
|
|
1148
1515
|
return await super.getPrimaryVideoTrack(query);
|
|
1149
1516
|
}
|
|
1150
1517
|
async getPrimaryAudioTrack(query) {
|
|
1151
1518
|
return await super.getPrimaryAudioTrack(query);
|
|
1152
1519
|
}
|
|
1520
|
+
addSubtitleTrack(source, metadata = {}) {
|
|
1521
|
+
const sourceWithRootPath = this.#takeSubtitleSourceRef(source).source;
|
|
1522
|
+
if (typeof sourceWithRootPath.rootPath !== "string") throw new TypeError("source must provide a string rootPath.");
|
|
1523
|
+
const pairWith = this.#toPairableVideoTracks(metadata.pairWith);
|
|
1524
|
+
const backing = new ExternalSubtitleTrackBacking({
|
|
1525
|
+
id: this.#nextCustomSubtitleTrackId++,
|
|
1526
|
+
number: this.#nextCustomSubtitleTrackNumber++,
|
|
1527
|
+
pairingMask: 0n,
|
|
1528
|
+
source: sourceWithRootPath,
|
|
1529
|
+
codec: metadata.codec,
|
|
1530
|
+
codecString: metadata.codecString,
|
|
1531
|
+
disposition: metadata.disposition,
|
|
1532
|
+
languageCode: metadata.languageCode,
|
|
1533
|
+
name: metadata.name
|
|
1534
|
+
});
|
|
1535
|
+
this.#pairSubtitleBacking(backing, pairWith);
|
|
1536
|
+
this.#customSubtitleBackings.push(backing);
|
|
1537
|
+
return this._wrapBackingAsTrack(backing);
|
|
1538
|
+
}
|
|
1539
|
+
#takeSubtitleSourceRef(source) {
|
|
1540
|
+
const rawSource = source instanceof SourceRef ? source.source : source;
|
|
1541
|
+
if (!(rawSource instanceof Object) || !("rootPath" in rawSource) || !("ref" in rawSource) || typeof rawSource.ref !== "function") throw new TypeError("source must be a pathed source such as UrlSource or FilePathSource.");
|
|
1542
|
+
const ref = rawSource.ref();
|
|
1543
|
+
this._sourceRefs.push(ref);
|
|
1544
|
+
return ref;
|
|
1545
|
+
}
|
|
1546
|
+
#toPairableVideoTracks(pairWith) {
|
|
1547
|
+
if (!pairWith) return [];
|
|
1548
|
+
const tracks = this.#isIterable(pairWith) ? [...pairWith] : [pairWith];
|
|
1549
|
+
for (const track of tracks) {
|
|
1550
|
+
if (track.input !== this) throw new TypeError("pairWith tracks must belong to the same input instance.");
|
|
1551
|
+
if (track.type !== "video") throw new TypeError("pairWith only accepts video tracks.");
|
|
1552
|
+
}
|
|
1553
|
+
return tracks;
|
|
1554
|
+
}
|
|
1555
|
+
#isIterable(value) {
|
|
1556
|
+
return typeof value === "object" && value !== null && Symbol.iterator in value;
|
|
1557
|
+
}
|
|
1558
|
+
#pairSubtitleBacking(subtitleBacking, videoTracks) {
|
|
1559
|
+
for (const track of videoTracks) {
|
|
1560
|
+
const bit = this.#allocatePairingBit();
|
|
1561
|
+
this.#appendPairingMask(subtitleBacking, bit);
|
|
1562
|
+
this.#appendPairingMask(track._backing, bit);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
#allocatePairingBit() {
|
|
1566
|
+
const nextIndex = this.#nextPairingBitIndex ?? this.#getInitialPairingBitIndex();
|
|
1567
|
+
this.#nextPairingBitIndex = nextIndex + 1n;
|
|
1568
|
+
return 1n << nextIndex;
|
|
1569
|
+
}
|
|
1570
|
+
#getInitialPairingBitIndex() {
|
|
1571
|
+
const loadedBackings = [...this._trackBackingsCache ?? []];
|
|
1572
|
+
let maxBitIndex = -1n;
|
|
1573
|
+
for (const backing of [...loadedBackings, ...this.#customSubtitleBackings]) {
|
|
1574
|
+
const mask = backing.getPairingMask?.() ?? 0n;
|
|
1575
|
+
if (mask === 0n) continue;
|
|
1576
|
+
const bitIndex = BigInt(mask.toString(2).length - 1);
|
|
1577
|
+
if (bitIndex > maxBitIndex) maxBitIndex = bitIndex;
|
|
1578
|
+
}
|
|
1579
|
+
return maxBitIndex >= 0n ? maxBitIndex + 1n : CUSTOM_PAIRING_BIT_START;
|
|
1580
|
+
}
|
|
1581
|
+
#appendPairingMask(backing, mask) {
|
|
1582
|
+
const patchedBacking = backing;
|
|
1583
|
+
patchedBacking[EXTRA_PAIRING_MASK] = (patchedBacking[EXTRA_PAIRING_MASK] ?? 0n) | mask;
|
|
1584
|
+
if (patchedBacking[ORIGINAL_GET_PAIRING_MASK]) return;
|
|
1585
|
+
patchedBacking[ORIGINAL_GET_PAIRING_MASK] = backing.getPairingMask?.bind(backing) ?? (() => 0n);
|
|
1586
|
+
Object.assign(backing, { getPairingMask: () => (patchedBacking[ORIGINAL_GET_PAIRING_MASK]?.() ?? 0n) | (patchedBacking[EXTRA_PAIRING_MASK] ?? 0n) });
|
|
1587
|
+
}
|
|
1153
1588
|
};
|
|
1154
1589
|
//#endregion
|
|
1155
1590
|
//#region src/dash/dash-segmented-input.ts
|
|
@@ -1660,7 +2095,8 @@ const createDashTrack = (params) => {
|
|
|
1660
2095
|
if (width) track.width = Number(width);
|
|
1661
2096
|
if (height) track.height = Number(height);
|
|
1662
2097
|
track.frameRate = frameRate ?? getDashFrameRate(representation);
|
|
1663
|
-
if (
|
|
2098
|
+
if (supplementalProps.length > 0 || essentialProps.length > 0) track.colorSpace = parseColorSpace(supplementalProps, essentialProps);
|
|
2099
|
+
if (track.codecString && (supplementalProps.length > 0 || essentialProps.length > 0)) track.dynamicRange = parseDynamicRange(track.codecString, supplementalProps, essentialProps);
|
|
1664
2100
|
} else if (track.type === "audio") {
|
|
1665
2101
|
if (accessibilities) track.descriptive = checkIsDescriptive(accessibilities);
|
|
1666
2102
|
if (supplementalProps) track.joc = getDolbyDigitalPlusComplexityIndex(supplementalProps);
|
|
@@ -2258,7 +2694,7 @@ var DashInputVideoTrackBacking = class extends DashTrackBackingBase {
|
|
|
2258
2694
|
return 0;
|
|
2259
2695
|
}
|
|
2260
2696
|
async getColorSpace() {
|
|
2261
|
-
return this.delegate((track) => track.getColorSpace());
|
|
2697
|
+
return this.internalTrack.track.colorSpace ?? this.delegate((track) => track.getColorSpace());
|
|
2262
2698
|
}
|
|
2263
2699
|
async canBeTransparent() {
|
|
2264
2700
|
return this.delegate((track) => track.canBeTransparent());
|
|
@@ -2321,11 +2757,11 @@ var DashInputFormat = class extends InputFormat {
|
|
|
2321
2757
|
const DASH = new DashInputFormat();
|
|
2322
2758
|
const DASH_FORMATS = [
|
|
2323
2759
|
DASH,
|
|
2324
|
-
MP4,
|
|
2760
|
+
MP4$1,
|
|
2325
2761
|
QTFF,
|
|
2326
2762
|
WEBM,
|
|
2327
2763
|
MATROSKA,
|
|
2328
|
-
MP3,
|
|
2764
|
+
MP3$1,
|
|
2329
2765
|
ADTS
|
|
2330
2766
|
];
|
|
2331
2767
|
//#endregion
|
|
@@ -2338,5 +2774,6 @@ var Input = class extends SegmentedMediabunnyInput {
|
|
|
2338
2774
|
const isInput = (value) => value instanceof Input;
|
|
2339
2775
|
const getSegmentedInput = (track) => track.getSegmentedInput();
|
|
2340
2776
|
const getSegments = async (track) => track.getSegments();
|
|
2777
|
+
const ALL_FORMATS = [...ALL_FORMATS$1, DASH];
|
|
2341
2778
|
//#endregion
|
|
2342
|
-
export { DASH, DASH_FORMATS, FilePathSource, HLS_FORMATS, Input, UrlSource, asc, desc, getSegmentedInput, getSegments, isInput, prefer, preserveSubtitleBackingsOnInput };
|
|
2779
|
+
export { ALL_FORMATS, DASH, DASH_FORMATS, FilePathSource, HLS, HLS_FORMATS, Input, MP3, MP4, UrlSource, asc, desc, getSegmentedInput, getSegments, isInput, prefer, preserveSubtitleBackingsOnInput };
|