dasha 3.0.5 → 3.1.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/lib/subtitle.js DELETED
@@ -1,120 +0,0 @@
1
- 'use strict';
2
-
3
- const { parseMimes } = require('./track');
4
- const { parseBitrate, parseSize } = require('./util');
5
-
6
- const SUBTITLE_CODECS = {
7
- SubRip: 'SRT', // https://wikipedia.org/wiki/SubRip
8
- SubStationAlpha: 'SSA', // https://wikipedia.org/wiki/SubStation_Alpha
9
- SubStationAlphav4: 'ASS', // https://wikipedia.org/wiki/SubStation_Alpha#Advanced_SubStation_Alpha=
10
- TimedTextMarkupLang: 'TTML', // https://wikipedia.org/wiki/Timed_Text_Markup_Language
11
- WebVTT: 'VTT', // https://wikipedia.org/wiki/WebVTT
12
- // MPEG-DASH box-encapsulated subtitle formats
13
- fTTML: 'STPP', // https://www.w3.org/TR/2018/REC-ttml-imsc1.0.1-20180424
14
- fVTT: 'WVTT', // https://www.w3.org/TR/webvtt1
15
- };
16
-
17
- const parseSubtitleCodecFromMime = (mime) => {
18
- const target = mime.toLowerCase().trim().split('.')[0];
19
- switch (target) {
20
- case 'srt':
21
- case 'x-subrip':
22
- return SUBTITLE_CODECS.SubRip;
23
- case 'ssa':
24
- return SUBTITLE_CODECS.SubStationAlpha;
25
- case 'ass':
26
- return SUBTITLE_CODECS.SubStationAlphav4;
27
- case 'ttml':
28
- return SUBTITLE_CODECS.TimedTextMarkupLang;
29
- case 'vtt':
30
- return SUBTITLE_CODECS.WebVTT;
31
- case 'stpp':
32
- return SUBTITLE_CODECS.fTTML;
33
- case 'wvtt':
34
- return SUBTITLE_CODECS.fVTT;
35
- default:
36
- throw new Error(`The MIME ${mime} is not supported as subtitle codec`);
37
- }
38
- };
39
-
40
- const parseSubtitleCodec = (codecs) => {
41
- const mimes = parseMimes(codecs);
42
- for (const mime of mimes) {
43
- try {
44
- return parseSubtitleCodecFromMime(mime);
45
- } catch (e) {
46
- continue;
47
- }
48
- }
49
- throw new Error(`No MIME types matched any supported Subtitle Codecs in ${codecs}`);
50
- };
51
-
52
- const checkIsClosedCaption = (roles = []) => {
53
- for (const role of roles) {
54
- const isClosedCaption =
55
- role.attributes.schemeIdUri === 'urn:mpeg:dash:role:2011' &&
56
- role.attributes.value === 'caption';
57
- if (isClosedCaption) return true;
58
- }
59
- return false;
60
- };
61
-
62
- const checkIsSdh = (accessibilities = []) => {
63
- for (const accessibility of accessibilities) {
64
- const { schemeIdUri, value } = accessibility.attributes;
65
- const isSdh = schemeIdUri === 'urn:tva:metadata:cs:AudioPurposeCS:2007' && value === '2';
66
- if (isSdh) return true;
67
- }
68
- return false;
69
- };
70
-
71
- const checkIsForced = (roles = []) => {
72
- for (const role of roles) {
73
- const isForced =
74
- role.attributes.schemeIdUri === 'urn:mpeg:dash:role:2011' &&
75
- (role.attributes.value === 'forced-subtitle' || role.attributes.value === 'forced_subtitle');
76
- if (isForced) return true;
77
- }
78
- return false;
79
- };
80
-
81
- const createSubtitleTrack = ({
82
- id,
83
- label,
84
- bitrate,
85
- duration,
86
- type,
87
- codec,
88
- isClosedCaption,
89
- isSdh,
90
- isForced,
91
- language,
92
- segments,
93
- }) => {
94
- const parsedBitrate = parseBitrate(Number(bitrate));
95
- const size = duration ? parseSize(Number(bitrate), Number(duration)) : undefined;
96
- return {
97
- id,
98
- label,
99
- bitrate: parsedBitrate,
100
- size,
101
- type,
102
- codec,
103
- isClosedCaption,
104
- isSdh,
105
- isForced,
106
- segments,
107
- language,
108
- toString() {
109
- return ['SUBTITLE', `[${codec}]`, language].join(' | ');
110
- },
111
- };
112
- };
113
-
114
- module.exports = {
115
- parseSubtitleCodec,
116
- checkIsClosedCaption,
117
- checkIsSdh,
118
- checkIsForced,
119
- createSubtitleTrack,
120
- };
package/lib/track.js DELETED
@@ -1,107 +0,0 @@
1
- 'use strict';
2
-
3
- const { getBestTrack } = require('./util');
4
-
5
- const parseMimes = (codecs) =>
6
- codecs
7
- .toLowerCase()
8
- .split(',')
9
- .map((codec) => codec.trim().split('.')[0]);
10
-
11
- const createResolutionFilter = (videos) => {
12
- return ({ width, height }) => {
13
- return videos.filter(
14
- (track) => (!width || track.width === width) && (!height || track.height === height)
15
- );
16
- };
17
- };
18
-
19
- const createCodecFilter = (tracks) => {
20
- return (codecs) => {
21
- if (!codecs?.length) return tracks;
22
- const results = tracks.filter((track) => codecs.includes(track.codec));
23
- results.sort((a, b) => b.bitrate.bps - a.bitrate.bps);
24
- return results;
25
- };
26
- };
27
-
28
- const createVideoCodecFilter = (videos) => createCodecFilter(videos);
29
-
30
- const createVideoQualityFilter = (videos) => {
31
- return (quality) => {
32
- if (!quality) return [getBestTrack(videos)];
33
- const trackQuality = String(quality).includes('p') ? quality : `${quality}p`;
34
- const results = videos.filter((track) => track.quality === trackQuality);
35
- results.sort((a, b) => b.bitrate.bps - a.bitrate.bps);
36
- return results.length ? results : [getBestTrack(videos)];
37
- };
38
- };
39
-
40
- const createAudioCodecFilter = (audios) => createCodecFilter(audios);
41
-
42
- const createAudioLanguageFilter = (audios) => {
43
- return (languages = [], maxTracksPerLanguage) => {
44
- if (!languages.length) {
45
- for (const audio of audios) {
46
- const alreadyAdded = languages.includes(audio.language);
47
- if (!alreadyAdded) languages.push(audio.language);
48
- }
49
- }
50
- const filtered = [];
51
- for (const language of languages) {
52
- const tracks = audios.filter((track) => track.language?.startsWith(language));
53
- filtered.push(...tracks);
54
- }
55
- const results = [];
56
- const filteredLanguages = [...new Set(filtered.map((track) => track.language))];
57
- for (const language of filteredLanguages) {
58
- const tracks = filtered
59
- .filter((track) => track.language === language)
60
- .slice(0, maxTracksPerLanguage);
61
- results.push(...tracks);
62
- }
63
- return results;
64
- };
65
- };
66
-
67
- const createAudioChannelsFilter = (audios) => {
68
- return (channels) => {
69
- if (!channels) return audios;
70
- const value = typeof channels === 'string' ? parseFloat(channels) : channels;
71
- return audios.filter((track) => track.channels === value);
72
- };
73
- };
74
-
75
- const createSubtitleLanguageFilter = (subtitles) => {
76
- return (languages) => {
77
- if (!languages.length) return subtitles;
78
- return subtitles.filter((track) =>
79
- languages.some(
80
- (language) => track.language?.startsWith(language) || track.label?.startsWith(language)
81
- )
82
- );
83
- };
84
- };
85
-
86
- const filterByResolution = (tracks, resolution) => createResolutionFilter(tracks)(resolution);
87
- const filterByQuality = (tracks, quality) => createVideoQualityFilter(tracks)(quality);
88
- const filterByCodecs = (tracks, codecs) => createCodecFilter(tracks)(codecs);
89
- const filterByLanguages = (tracks, languages, maxTracksPerLanguage) =>
90
- createAudioLanguageFilter(tracks)(languages, maxTracksPerLanguage);
91
- const filterByChannels = (tracks, channels) => createAudioChannelsFilter(tracks)(channels);
92
-
93
- module.exports = {
94
- parseMimes,
95
- createResolutionFilter,
96
- createVideoCodecFilter,
97
- createVideoQualityFilter,
98
- createAudioCodecFilter,
99
- createAudioLanguageFilter,
100
- createAudioChannelsFilter,
101
- createSubtitleLanguageFilter,
102
- filterByResolution,
103
- filterByQuality,
104
- filterByCodecs,
105
- filterByLanguages,
106
- filterByChannels,
107
- };
package/lib/util.js DELETED
@@ -1,97 +0,0 @@
1
- 'use strict';
2
-
3
- const formatBytes = (bytes, sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']) => {
4
- if (bytes == 0) return `0 ${sizes[0]}`;
5
- const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
6
- if (i == 0) return bytes + ' ' + sizes[i];
7
- return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
8
- };
9
-
10
- const parseSize = (bandwidth, duration) => ({
11
- b: bandwidth * duration,
12
- kb: (bandwidth / 1000) * duration,
13
- mb: (bandwidth / 8e6) * duration,
14
- gb: (bandwidth / 8e9) * duration,
15
- toString() {
16
- return formatBytes(bandwidth * duration);
17
- },
18
- });
19
-
20
- const parseBitrate = (bandwidth) => ({
21
- bps: bandwidth,
22
- kbps: bandwidth / 1000,
23
- mbps: bandwidth / 8e6,
24
- gbps: bandwidth / 8e9,
25
- toString() {
26
- return formatBytes(bandwidth, ['bps', 'Kbps', 'Mbps', 'Gbps']);
27
- },
28
- });
29
-
30
- const qualities = [
31
- { width: 7680, height: 4320 },
32
- { width: 3840, height: 2160 },
33
- { width: 2560, height: 1440 },
34
- { width: 1920, height: 1080 },
35
- { width: 1280, height: 720 },
36
- { width: 1024, height: 576 },
37
- { width: 854, height: 480 },
38
- { width: 640, height: 360 },
39
- { width: 426, height: 240 },
40
- { width: 256, height: 144 },
41
- ];
42
- const getWidth = (height) => qualities.find((q) => q.height === height)?.width;
43
- const getHeight = (width) => qualities.find((q) => q.width === width)?.height;
44
- const getQualityLabel = (resolution) => `${getHeight(resolution.width) || resolution.height}p`;
45
-
46
- const getBestTrack = (tracks) => {
47
- const maxBitrate = Math.max(...tracks.map((track) => track.bitrate.bps));
48
- return tracks.find((track) => track.bitrate.bps === maxBitrate);
49
- };
50
-
51
- const parseDuration = (str) => {
52
- const SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
53
- const SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
54
- const SECONDS_IN_DAY = 24 * 60 * 60;
55
- const SECONDS_IN_HOUR = 60 * 60;
56
- const SECONDS_IN_MIN = 60;
57
-
58
- // P10Y10M10DT10H10M10.1S
59
- const durationRegex =
60
- /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
61
- const match = durationRegex.exec(str);
62
-
63
- if (!match) {
64
- return 0;
65
- }
66
-
67
- const [year, month, day, hour, minute, second] = match.slice(1);
68
-
69
- return (
70
- parseFloat(year || 0) * SECONDS_IN_YEAR +
71
- parseFloat(month || 0) * SECONDS_IN_MONTH +
72
- parseFloat(day || 0) * SECONDS_IN_DAY +
73
- parseFloat(hour || 0) * SECONDS_IN_HOUR +
74
- parseFloat(minute || 0) * SECONDS_IN_MIN +
75
- parseFloat(second || 0)
76
- );
77
- };
78
-
79
- const isLanguageTagValid = (value) => {
80
- try {
81
- Intl.getCanonicalLocales(value);
82
- return true;
83
- } catch (err) {
84
- return false;
85
- }
86
- };
87
-
88
- module.exports = {
89
- parseSize,
90
- parseBitrate,
91
- getWidth,
92
- getHeight,
93
- getQualityLabel,
94
- getBestTrack,
95
- parseDuration,
96
- isLanguageTagValid,
97
- };
package/lib/video.js DELETED
@@ -1,158 +0,0 @@
1
- const { parseBitrate, getQualityLabel, parseSize } = require('./util');
2
-
3
- const VIDEO_CODECS = {
4
- avc: 'H.264',
5
- hevc: 'H.265',
6
- vc1: 'VC-1',
7
- vp8: 'VP8',
8
- vp9: 'VP9',
9
- av1: 'AV1',
10
- };
11
-
12
- const DYNAMIC_RANGE = {
13
- sdr: 'SDR', // Standart Dynamic Range
14
- hlg: 'HLG', // Hybrid log-gamma (HDR)
15
- hdr10: 'HDR10',
16
- hdr10p: 'HDR10+',
17
- dv: 'DV', // Dolby Vision
18
- };
19
-
20
- const PRIMARIES = {
21
- Unspecified: 0,
22
- BT_709: 1,
23
- BT_601_625: 5,
24
- BT_601_525: 6,
25
- BT_2020_and_2100: 9,
26
- SMPTE_ST_2113_and_EG_4321: 12, // P3D65
27
- };
28
-
29
- const TRANSFER = {
30
- Unspecified: 0,
31
- BT_709: 1,
32
- BT_601: 6,
33
- BT_2020: 14,
34
- BT_2100: 15,
35
- BT_2100_PQ: 16,
36
- BT_2100_HLG: 18,
37
- };
38
-
39
- const MATRIX = {
40
- RGB: 0,
41
- YCbCr_BT_709: 1,
42
- YCbCr_BT_601_625: 5,
43
- YCbCr_BT_601_525: 6,
44
- YCbCr_BT_2020_and_2100: 9, // YCbCr BT.2100 shares the same CP
45
- ICtCp_BT_2100: 14,
46
- };
47
-
48
- const parseVideoCodecFromMime = (mime) => {
49
- const target = mime.toLowerCase().trim().split('.')[0];
50
- const avc = ['avc1', 'avc2', 'avc3', 'dva1', 'dvav'];
51
- const hevc = ['hev1', 'hev2', 'hev3', 'hvc1', 'hvc2', 'hvc3', 'dvh1', 'dvhe', 'lhv1', 'lhe1'];
52
- const vc1 = ['vc-1'];
53
- const vp8 = ['vp08', 'vp8'];
54
- const vp9 = ['vp09', 'vp9'];
55
- const av1 = ['av01'];
56
- if (avc.includes(target)) return VIDEO_CODECS.avc;
57
- if (hevc.includes(target)) return VIDEO_CODECS.hevc;
58
- if (vc1.includes(target)) return VIDEO_CODECS.hevc;
59
- if (vp8.includes(target)) return VIDEO_CODECS.vp8;
60
- if (vp9.includes(target)) return VIDEO_CODECS.vp9;
61
- if (av1.includes(target)) return VIDEO_CODECS.av1;
62
- throw new Error(`The MIME ${mime} is not supported as video codec`);
63
- };
64
-
65
- const parseDynamicRangeFromCicp = (primaries, transfer, matrix) => {
66
- // While not part of any standard, it is typically used as a PAL variant of Transfer.BT_601=6.
67
- // i.e. where Transfer 6 would be for BT.601-NTSC and Transfer 5 would be for BT.601-PAL.
68
- // The codebase is currently agnostic to either, so a manual conversion to 6 is done.
69
- if (transfer == 5) transfer = TRANSFER.BT_601;
70
-
71
- if (
72
- primaries == PRIMARIES.Unspecified &&
73
- transfer == TRANSFER.Unspecified &&
74
- matrix == MATRIX.RGB
75
- )
76
- return DYNAMIC_RANGE.sdr;
77
- else if ([PRIMARIES.BT_601_625, PRIMARIES.BT_601_525].includes(primaries))
78
- return DYNAMIC_RANGE.sdr;
79
- else if (TRANSFER.BT_2100_PQ === transfer) return DYNAMIC_RANGE.hdr10;
80
- else if (TRANSFER.BT_2100_HLG === transfer) return DYNAMIC_RANGE.hlg;
81
- else return DYNAMIC_RANGE.sdr;
82
- };
83
-
84
- const createVideoTrack = ({
85
- id,
86
- label,
87
- type,
88
- codec,
89
- dynamicRange,
90
- contentProtection,
91
- bitrate,
92
- duration,
93
- width,
94
- height,
95
- fps,
96
- language,
97
- segments,
98
- }) => {
99
- const parsedBitrate = parseBitrate(Number(bitrate));
100
- const parsedWidth = Number(width);
101
- const parsedHeight = Number(height);
102
- const size = duration ? parseSize(Number(bitrate), Number(duration)) : undefined;
103
- return {
104
- id,
105
- label,
106
- type,
107
- codec,
108
- bitrate: parsedBitrate,
109
- size,
110
- protection: contentProtection,
111
- segments,
112
- dynamicRange,
113
- language,
114
- width: parsedWidth,
115
- height: parsedHeight,
116
- fps: Number(fps),
117
- quality: getQualityLabel({ width: parsedWidth, height: parsedHeight }),
118
- toString() {
119
- return [
120
- 'VIDEO',
121
- `[${codec}, ${dynamicRange}]`,
122
- language,
123
- `${width}x${height} @ ${parsedBitrate.kbps} kb/s, ${fps} FPS`,
124
- ].join(' | ');
125
- },
126
- };
127
- };
128
-
129
- const parseVideoCodec = (codecs) => {
130
- for (const codec of codecs.toLowerCase().split(',')) {
131
- const mime = codec.trim().split('.')[0];
132
- try {
133
- return parseVideoCodecFromMime(mime);
134
- } catch (e) {
135
- continue;
136
- }
137
- }
138
- throw new Error(`No MIME types matched any supported Video Codecs in ${codecs}`);
139
- };
140
-
141
- const parseDynamicRange = (codecs, supplementalProps = [], essentialProps = []) => {
142
- const dv = ['dva1', 'dvav', 'dvhe', 'dvh1'];
143
- if (dv.some((value) => codecs.startsWith(value))) return DYNAMIC_RANGE.dv;
144
- const primariesScheme = 'urn:mpeg:mpegB:cicp:ColourPrimaries';
145
- const transferScheme = 'urn:mpeg:mpegB:cicp:TransferCharacteristics';
146
- const matrixScheme = 'urn:mpeg:mpegB:cicp:MatrixCoefficients';
147
- const allProps = [...essentialProps, ...supplementalProps];
148
- const getValues = (scheme) =>
149
- allProps
150
- .filter((prop) => prop.attributes.schemeIdUri === scheme)
151
- .map((prop) => parseInt(prop.attributes.value));
152
- const primaries = getValues(primariesScheme).reduce((acc, current) => acc + current, 0);
153
- const transfer = getValues(transferScheme).reduce((acc, current) => acc + current, 0);
154
- const matrix = getValues(matrixScheme).reduce((acc, current) => acc + current, 0);
155
- return parseDynamicRangeFromCicp(primaries, transfer, matrix);
156
- };
157
-
158
- module.exports = { parseVideoCodec, parseDynamicRange, createVideoTrack, VIDEO_CODECS };