tiny-essentials 1.29.0 → 1.29.2
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/changelog/1/29/1.md +11 -0
- package/changelog/1/29/2.md +82 -0
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyFetchLoader.min.js +1 -0
- package/dist/v1/TinyImageLoader.min.js +1 -0
- package/dist/v1/TinyMediaCache.min.js +1 -0
- package/dist/v1/TinyMediaLoader.min.js +1 -0
- package/dist/v1/basics/index.cjs +5 -0
- package/dist/v1/basics/index.d.mts +2 -1
- package/dist/v1/basics/index.d.mts.map +1 -1
- package/dist/v1/basics/index.mjs +2 -1
- package/dist/v1/basics/media/youtube.cjs +275 -0
- package/dist/v1/basics/media/youtube.d.mts +107 -0
- package/dist/v1/basics/media/youtube.d.mts.map +1 -0
- package/dist/v1/basics/media/youtube.mjs +214 -0
- package/dist/v1/index.cjs +13 -0
- package/dist/v1/index.d.mts +6 -1
- package/dist/v1/index.d.mts.map +1 -1
- package/dist/v1/index.mjs +7 -2
- package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.cjs +0 -1
- package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.d.mts +0 -1
- package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.d.mts.map +1 -1
- package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.mjs +0 -1
- package/dist/v1/libs/fs/plugins/OPFS/index.cjs +0 -8
- package/dist/v1/libs/fs/plugins/OPFS/index.d.mts +0 -8
- package/dist/v1/libs/fs/plugins/OPFS/index.d.mts.map +1 -1
- package/dist/v1/libs/fs/plugins/OPFS/index.mjs +0 -8
- package/dist/v1/libs/html/TinyHtml.cjs +1 -1
- package/dist/v1/libs/html/TinyHtml.d.mts +2 -2
- package/dist/v1/libs/html/TinyHtml.d.mts.map +1 -1
- package/dist/v1/libs/html/TinyHtml.mjs +1 -1
- package/dist/v1/libs/html/loader/TinyMediaLoader.cjs +1999 -0
- package/dist/v1/libs/html/loader/TinyMediaLoader.d.mts +999 -0
- package/dist/v1/libs/html/loader/TinyMediaLoader.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/TinyMediaLoader.mjs +1852 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyAudioLoader.cjs +77 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyAudioLoader.d.mts +103 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyAudioLoader.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyAudioLoader.mjs +67 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyContentLoader.cjs +630 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyContentLoader.d.mts +138 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyContentLoader.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyContentLoader.mjs +576 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyVideoLoader.cjs +70 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyVideoLoader.d.mts +100 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyVideoLoader.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/beta/formats/TinyVideoLoader.mjs +60 -0
- package/dist/v1/libs/html/loader/beta/probe/TinyMediaProbe.cjs +946 -0
- package/dist/v1/libs/html/loader/beta/probe/TinyMediaProbe.d.mts +67 -0
- package/dist/v1/libs/html/loader/beta/probe/TinyMediaProbe.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/beta/probe/TinyMediaProbe.mjs +896 -0
- package/dist/v1/libs/html/loader/formats/TinyFetchLoader.cjs +225 -0
- package/dist/v1/libs/html/loader/formats/TinyFetchLoader.d.mts +149 -0
- package/dist/v1/libs/html/loader/formats/TinyFetchLoader.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/formats/TinyFetchLoader.mjs +197 -0
- package/dist/v1/libs/html/loader/formats/TinyImageLoader.cjs +1454 -0
- package/dist/v1/libs/html/loader/formats/TinyImageLoader.d.mts +200 -0
- package/dist/v1/libs/html/loader/formats/TinyImageLoader.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/formats/TinyImageLoader.mjs +1317 -0
- package/dist/v1/libs/html/loader/probe/TinyImageProbe.cjs +668 -0
- package/dist/v1/libs/html/loader/probe/TinyImageProbe.d.mts +83 -0
- package/dist/v1/libs/html/loader/probe/TinyImageProbe.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/probe/TinyImageProbe.mjs +626 -0
- package/dist/v1/libs/html/loader/utils/TinyBufferReader.cjs +277 -0
- package/dist/v1/libs/html/loader/utils/TinyBufferReader.d.mts +118 -0
- package/dist/v1/libs/html/loader/utils/TinyBufferReader.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/utils/TinyBufferReader.mjs +247 -0
- package/dist/v1/libs/html/loader/utils/TinyIsoBox.cjs +128 -0
- package/dist/v1/libs/html/loader/utils/TinyIsoBox.d.mts +49 -0
- package/dist/v1/libs/html/loader/utils/TinyIsoBox.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/utils/TinyIsoBox.mjs +116 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaCache.cjs +796 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaCache.d.mts +354 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaCache.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaCache.mjs +734 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaMirror.cjs +302 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaMirror.d.mts +256 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaMirror.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaMirror.mjs +280 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaProgress.cjs +194 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaProgress.d.mts +138 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaProgress.d.mts.map +1 -0
- package/dist/v1/libs/html/loader/utils/TinyMediaProgress.mjs +175 -0
- package/dist/v1/libs/tools/TinyPkgExportValidator.cjs +359 -55
- package/dist/v1/libs/tools/TinyPkgExportValidator.d.mts +64 -6
- package/dist/v1/libs/tools/TinyPkgExportValidator.d.mts.map +1 -1
- package/dist/v1/libs/tools/TinyPkgExportValidator.mjs +320 -57
- package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.cjs +0 -1
- package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.d.mts +0 -1
- package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.d.mts.map +1 -1
- package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.mjs +0 -1
- package/dist/v1/webTemplates/bootstrap/5.3/scss/background/_background-grid.scss +15 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/components/buttons/_buttons-contrast.scss +208 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/components/buttons/_buttons-ghost.scss +89 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/components/icon/_cube-icon.scss +65 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/components/icon/_sphere-pulse.scss +39 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_badges.scss +90 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_bootstrap-overrides.scss +495 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_buttons.scss +390 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_colors.scss +213 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_effects.scss +153 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_forms-tones.scss +69 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_forms.scss +323 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_global.scss +31 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_links.scss +47 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_tables.scss +25 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_theme.scss +21 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_tokens.scss +161 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_utilities.scss +81 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_variables.scss +63 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/_form-tone-mixins.scss +236 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/_sizing.scss +68 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/_tone-mixins.scss +392 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/_typography.scss +112 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/background/_tiny_gradient_1.scss +131 -0
- package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/colors/_pink.scss +47 -0
- package/dist/v1/webTemplates/multiformats/14.0/IPFSHasher/index.cjs +2 -2
- package/docs/v1/Exports.md +33 -2
- package/docs/v1/README.md +9 -0
- package/docs/v1/basics/youtube.md +204 -0
- package/docs/v1/libs/html/loader/TinyFetchLoader.md +114 -0
- package/docs/v1/libs/html/loader/TinyImageLoader.md +141 -0
- package/docs/v1/libs/html/loader/TinyMediaCache.md +166 -0
- package/docs/v1/libs/html/loader/TinyMediaLoader.md +300 -0
- package/docs/v1/libs/tools/TinyPkgExportValidator.md +55 -0
- package/docs/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters.md +0 -4
- package/docs/v1/webTemplates/bootstrap/5.3/DeepStrap.md +177 -0
- package/package.json +281 -17
- /package/dist/v1/webTemplates/bootstrap/5.3/scss/input/{Placeholder-Colors-Fix.scss → _placeholder-colors-fix.scss} +0 -0
- /package/dist/v1/webTemplates/bootstrap/5.3/scss/spacing/{Spacing-Plus.scss → _spacing-plus.scss} +0 -0
|
@@ -0,0 +1,896 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Probes the intrinsic dimensions of a video stream by parsing
|
|
3
|
+
* its container header. It never decodes a frame, so it works for codecs the
|
|
4
|
+
* browser cannot play.
|
|
5
|
+
*
|
|
6
|
+
* Supported containers: ISO base media (MP4, MOV, M4V, 3GP), Matroska/WebM,
|
|
7
|
+
* AVI, AMV, Ogg/Theora, MPEG-1/2 elementary streams, MPEG program streams
|
|
8
|
+
* (VOB), MPEG transport streams (TS, MTS, M2TS), FLV, ASF/WMV, RealMedia,
|
|
9
|
+
* NSV and RoQ.
|
|
10
|
+
*
|
|
11
|
+
* Note on production usage for MP4: Ensure your videos are optimized for web streaming
|
|
12
|
+
* (e.g., encoded with ffmpeg `-movflags faststart`). This places the `moov` atom at the
|
|
13
|
+
* beginning of the file, allowing the probe to resolve dimensions within the first few kilobytes
|
|
14
|
+
* instead of requiring the entire file to be loaded into memory.
|
|
15
|
+
*/
|
|
16
|
+
import TinyBufferReader, { TinyNeedMoreData } from '../../utils/TinyBufferReader.mjs';
|
|
17
|
+
import { findBox } from '../../utils/TinyIsoBox.mjs';
|
|
18
|
+
/**
|
|
19
|
+
* Represents the intrinsic dimensions of a media resource.
|
|
20
|
+
* @typedef {Object} MediaProbeSize
|
|
21
|
+
* @property {number} width - The display width in pixels.
|
|
22
|
+
* @property {number} height - The display height in pixels.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Describes a media container format and how to identify and parse its dimensions.
|
|
26
|
+
* @typedef {Object} MediaFormat
|
|
27
|
+
* @property {string} name - The container identifier.
|
|
28
|
+
* @property {(bytes: Uint8Array) => boolean} sniff - A function that tests if the provided bytes match the container's signature.
|
|
29
|
+
* @property {(reader: TinyBufferReader) => MediaProbeSize | null} parse - A function that parses the container to extract the media dimensions.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* The largest header the probe buffers before giving up.
|
|
33
|
+
* @type {number}
|
|
34
|
+
*/
|
|
35
|
+
const MAX_HEADER_BYTES = 8 * 1024 * 1024;
|
|
36
|
+
/**
|
|
37
|
+
* The EBML element identifiers the WebM parser descends into.
|
|
38
|
+
* @type {ReadonlySet<number>}
|
|
39
|
+
*/
|
|
40
|
+
const WEBM_CONTAINERS = new Set([0x18538067, 0x1654ae6b, 0xae, 0xe0]);
|
|
41
|
+
/**
|
|
42
|
+
* The H.264 profiles that carry the extra `chroma_format_idc` fields.
|
|
43
|
+
* @type {ReadonlySet<number>}
|
|
44
|
+
*/
|
|
45
|
+
const HIGH_PROFILES = new Set([100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135]);
|
|
46
|
+
/**
|
|
47
|
+
* Checks if a buffer starts with a specific byte signature at a given offset.
|
|
48
|
+
* @param {Uint8Array} bytes - The buffer to test.
|
|
49
|
+
* @param {readonly number[]} signature - The expected byte prefix.
|
|
50
|
+
* @param {number} [offset] - The offset at which to perform the test.
|
|
51
|
+
* @returns {boolean} True when the buffer starts with the signature at the specified offset.
|
|
52
|
+
*/
|
|
53
|
+
function matches(bytes, signature, offset = 0) {
|
|
54
|
+
if (bytes.length < offset + signature.length) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return signature.every((byte, index) => bytes[offset + index] === byte);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Strips H.264 emulation prevention bytes (0x000003) from a NAL unit to recover the raw bitstream.
|
|
61
|
+
* @param {Uint8Array} data - The raw NAL unit containing emulation bytes.
|
|
62
|
+
* @returns {Uint8Array} The unescaped NAL unit.
|
|
63
|
+
*/
|
|
64
|
+
function unescapeNal(data) {
|
|
65
|
+
const output = new Uint8Array(data.length);
|
|
66
|
+
let size = 0;
|
|
67
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
68
|
+
if (i >= 2 && data[i] === 0x03 && data[i - 1] === 0x00 && data[i - 2] === 0x00) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
output[size] = data[i];
|
|
72
|
+
size += 1;
|
|
73
|
+
}
|
|
74
|
+
return output.subarray(0, size);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parses an H.264 Sequence Parameter Set (SPS) NAL unit to extract the image dimensions.
|
|
78
|
+
* @param {Uint8Array} data - The SPS NAL unit, including its 1-byte NAL header.
|
|
79
|
+
* @returns {MediaProbeSize|null} The extracted width and height, or null when the SPS is malformed or unsupported.
|
|
80
|
+
*/
|
|
81
|
+
function readH264Sps(data) {
|
|
82
|
+
const bytes = unescapeNal(data);
|
|
83
|
+
let bit = 0;
|
|
84
|
+
const readBit = () => (bytes[bit >> 3] >> (7 - (bit++ & 7))) & 1;
|
|
85
|
+
/** @param {number} count */
|
|
86
|
+
const readBits = (count) => {
|
|
87
|
+
let value = 0;
|
|
88
|
+
for (let i = 0; i < count; i += 1) {
|
|
89
|
+
value = (value << 1) | readBit();
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
};
|
|
93
|
+
const readUe = () => {
|
|
94
|
+
let zeros = 0;
|
|
95
|
+
while (readBit() === 0) {
|
|
96
|
+
zeros += 1;
|
|
97
|
+
if (zeros > 32) {
|
|
98
|
+
throw new RangeError('Invalid Exp-Golomb code.');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return (1 << zeros) - 1 + (zeros > 0 ? readBits(zeros) : 0);
|
|
102
|
+
};
|
|
103
|
+
const readSe = () => {
|
|
104
|
+
const value = readUe();
|
|
105
|
+
return value & 1 ? (value + 1) >> 1 : -(value >> 1);
|
|
106
|
+
};
|
|
107
|
+
readBits(8);
|
|
108
|
+
const profileIdc = readBits(8);
|
|
109
|
+
readBits(8);
|
|
110
|
+
readBits(8);
|
|
111
|
+
readUe();
|
|
112
|
+
if (HIGH_PROFILES.has(profileIdc)) {
|
|
113
|
+
const chromaFormatIdc = readUe();
|
|
114
|
+
if (chromaFormatIdc === 3) {
|
|
115
|
+
readBit();
|
|
116
|
+
}
|
|
117
|
+
readUe();
|
|
118
|
+
readUe();
|
|
119
|
+
readBit();
|
|
120
|
+
if (readBit() === 1) {
|
|
121
|
+
const count = chromaFormatIdc !== 3 ? 8 : 12;
|
|
122
|
+
for (let i = 0; i < count; i += 1) {
|
|
123
|
+
if (readBit() === 1) {
|
|
124
|
+
let lastScale = 8;
|
|
125
|
+
let nextScale = 8;
|
|
126
|
+
const size = i < 6 ? 16 : 64;
|
|
127
|
+
for (let j = 0; j < size; j += 1) {
|
|
128
|
+
if (nextScale !== 0) {
|
|
129
|
+
nextScale = (lastScale + readSe() + 256) % 256;
|
|
130
|
+
}
|
|
131
|
+
lastScale = nextScale === 0 ? lastScale : nextScale;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
readUe();
|
|
138
|
+
const picOrderCntType = readUe();
|
|
139
|
+
if (picOrderCntType === 0) {
|
|
140
|
+
readUe();
|
|
141
|
+
}
|
|
142
|
+
else if (picOrderCntType === 1) {
|
|
143
|
+
readBit();
|
|
144
|
+
readSe();
|
|
145
|
+
readSe();
|
|
146
|
+
const num = readUe();
|
|
147
|
+
for (let i = 0; i < num; i += 1) {
|
|
148
|
+
readSe();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
readUe();
|
|
152
|
+
readBit();
|
|
153
|
+
const widthMbs = readUe() + 1;
|
|
154
|
+
const heightMapUnits = readUe() + 1;
|
|
155
|
+
const frameMbsOnly = readBit();
|
|
156
|
+
if (frameMbsOnly === 0) {
|
|
157
|
+
readBit();
|
|
158
|
+
}
|
|
159
|
+
readBit();
|
|
160
|
+
let cropLeft = 0;
|
|
161
|
+
let cropRight = 0;
|
|
162
|
+
let cropTop = 0;
|
|
163
|
+
let cropBottom = 0;
|
|
164
|
+
if (readBit() === 1) {
|
|
165
|
+
cropLeft = readUe();
|
|
166
|
+
cropRight = readUe();
|
|
167
|
+
cropTop = readUe();
|
|
168
|
+
cropBottom = readUe();
|
|
169
|
+
}
|
|
170
|
+
const width = widthMbs * 16 - (cropLeft + cropRight) * 2;
|
|
171
|
+
const height = (2 - frameMbsOnly) * heightMapUnits * 16 - (cropTop + cropBottom) * 2;
|
|
172
|
+
return width > 0 && height > 0 ? { width, height } : null;
|
|
173
|
+
}
|
|
174
|
+
/* -------------------------------------------------------------------------- */
|
|
175
|
+
/* ISO base media file format (MP4, MOV, M4V, 3GP, 3G2) */
|
|
176
|
+
/* -------------------------------------------------------------------------- */
|
|
177
|
+
/**
|
|
178
|
+
* Parses a resource using the ISO Base Media File Format (ISO BMFF) to extract dimensions.
|
|
179
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
180
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
181
|
+
*/
|
|
182
|
+
function parseIsoBmff(reader) {
|
|
183
|
+
const end = reader.limit;
|
|
184
|
+
const moov = findBox(reader, 0, end, new Set(['moov']));
|
|
185
|
+
if (moov) {
|
|
186
|
+
let cursor = moov.dataStart;
|
|
187
|
+
while (cursor + 8 <= moov.dataEnd) {
|
|
188
|
+
reader.seek(cursor);
|
|
189
|
+
const size = reader.uint32();
|
|
190
|
+
const type = reader.ascii(4);
|
|
191
|
+
if (size < 8) {
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
if (type === 'trak') {
|
|
195
|
+
const tkhd = findBox(reader, cursor + 8, cursor + size, new Set(['tkhd']));
|
|
196
|
+
if (tkhd) {
|
|
197
|
+
const version = reader.seek(tkhd.dataStart).uint8();
|
|
198
|
+
reader.seek(tkhd.start + (version === 1 ? 96 : 84));
|
|
199
|
+
const width = reader.uint32() / 65536;
|
|
200
|
+
const height = reader.uint32() / 65536;
|
|
201
|
+
if (width > 0 && height > 0) {
|
|
202
|
+
return { width: Math.round(width), height: Math.round(height) };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
cursor += size;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const ispe = findBox(reader, 0, end, new Set(['ispe']));
|
|
210
|
+
if (ispe) {
|
|
211
|
+
reader.seek(ispe.dataStart + 4);
|
|
212
|
+
const width = reader.uint32();
|
|
213
|
+
const height = reader.uint32();
|
|
214
|
+
if (width > 0 && height > 0) {
|
|
215
|
+
return { width, height };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
/* -------------------------------------------------------------------------- */
|
|
221
|
+
/* Matroska / WebM */
|
|
222
|
+
/* -------------------------------------------------------------------------- */
|
|
223
|
+
/**
|
|
224
|
+
* Parses a resource using the WebM/Matroska container format to extract dimensions.
|
|
225
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
226
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
227
|
+
*/
|
|
228
|
+
function parseWebm(reader) {
|
|
229
|
+
return scanWebm(reader, 0, reader.limit);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Scans a range of bytes for WebM/Matroska elements to locate video dimensions.
|
|
233
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
234
|
+
* @param {number} start - The absolute offset of the first byte in the range.
|
|
235
|
+
* @param {number} end - The absolute offset marking the end of the range.
|
|
236
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
237
|
+
*/
|
|
238
|
+
function scanWebm(reader, start, end) {
|
|
239
|
+
let offset = start;
|
|
240
|
+
while (offset < end) {
|
|
241
|
+
const id = readVint(reader, offset, true);
|
|
242
|
+
if (!id) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const size = readVint(reader, offset + id.length, false);
|
|
246
|
+
if (!size) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const dataStart = offset + id.length + size.length;
|
|
250
|
+
const dataEnd = dataStart + size.value;
|
|
251
|
+
if (dataEnd > end) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
if (id.value === 0xe0) {
|
|
255
|
+
return readWebmVideo(reader, dataStart, dataEnd);
|
|
256
|
+
}
|
|
257
|
+
if (WEBM_CONTAINERS.has(id.value)) {
|
|
258
|
+
const result = scanWebm(reader, dataStart, dataEnd);
|
|
259
|
+
if (result) {
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
offset = dataEnd;
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Parses the `Video` element of a WebM/Matroska stream to extract dimensions.
|
|
269
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
270
|
+
* @param {number} start - The absolute offset of the first byte of the `Video` payload.
|
|
271
|
+
* @param {number} end - The absolute offset marking the end of the `Video` payload.
|
|
272
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
273
|
+
*/
|
|
274
|
+
function readWebmVideo(reader, start, end) {
|
|
275
|
+
let width = 0;
|
|
276
|
+
let height = 0;
|
|
277
|
+
let offset = start;
|
|
278
|
+
while (offset < end) {
|
|
279
|
+
const id = readVint(reader, offset, true);
|
|
280
|
+
if (!id) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
const size = readVint(reader, offset + id.length, false);
|
|
284
|
+
if (!size) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
const dataStart = offset + id.length + size.length;
|
|
288
|
+
const dataEnd = dataStart + size.value;
|
|
289
|
+
if (dataEnd > end) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
if (id.value === 0xb0) {
|
|
293
|
+
width = readUint(reader, dataStart, size.value);
|
|
294
|
+
}
|
|
295
|
+
else if (id.value === 0xba) {
|
|
296
|
+
height = readUint(reader, dataStart, size.value);
|
|
297
|
+
}
|
|
298
|
+
offset = dataEnd;
|
|
299
|
+
}
|
|
300
|
+
return width > 0 && height > 0 ? { width, height } : null;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Decodes a Variable-length Integer (VINT) from the buffer.
|
|
304
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
305
|
+
* @param {number} offset - The absolute offset of the first byte of the integer.
|
|
306
|
+
* @param {boolean} keepMarker - Whether the length marker bit should be preserved in the returned value.
|
|
307
|
+
* @returns {{ value: number, length: number }|null} An object containing the decoded value and the number of bytes read, or null on failure.
|
|
308
|
+
*/
|
|
309
|
+
function readVint(reader, offset, keepMarker) {
|
|
310
|
+
const bytes = reader.buffer;
|
|
311
|
+
if (offset >= bytes.length) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
const first = bytes[offset];
|
|
315
|
+
if (first === 0) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
let length = 1;
|
|
319
|
+
let mask = 0x80;
|
|
320
|
+
while (length <= 8 && !(first & mask)) {
|
|
321
|
+
mask >>= 1;
|
|
322
|
+
length += 1;
|
|
323
|
+
}
|
|
324
|
+
if (length > 8 || offset + length > bytes.length) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
let value = keepMarker ? first : first & (mask - 1);
|
|
328
|
+
for (let i = 1; i < length; i += 1) {
|
|
329
|
+
value = value * 256 + bytes[offset + i];
|
|
330
|
+
}
|
|
331
|
+
return { value, length };
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Reads a raw unsigned integer of a specified length from the buffer.
|
|
335
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
336
|
+
* @param {number} offset - The absolute offset of the first byte of the integer.
|
|
337
|
+
* @param {number} length - The number of bytes to read.
|
|
338
|
+
* @returns {number} The decoded integer value.
|
|
339
|
+
*/
|
|
340
|
+
function readUint(reader, offset, length) {
|
|
341
|
+
const bytes = reader.buffer;
|
|
342
|
+
let value = 0;
|
|
343
|
+
for (let i = 0; i < length; i += 1) {
|
|
344
|
+
value = value * 256 + bytes[offset + i];
|
|
345
|
+
}
|
|
346
|
+
return value;
|
|
347
|
+
}
|
|
348
|
+
/* -------------------------------------------------------------------------- */
|
|
349
|
+
/* AVI / AMV */
|
|
350
|
+
/* -------------------------------------------------------------------------- */
|
|
351
|
+
/**
|
|
352
|
+
* Parses an AVI or AMV resource to extract dimensions.
|
|
353
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
354
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
355
|
+
*/
|
|
356
|
+
function parseAvi(reader) {
|
|
357
|
+
const end = reader.limit;
|
|
358
|
+
let offset = 12;
|
|
359
|
+
while (offset + 8 <= end) {
|
|
360
|
+
reader.seek(offset);
|
|
361
|
+
const id = reader.ascii(4);
|
|
362
|
+
const size = reader.uint32le();
|
|
363
|
+
if (id === 'avih') {
|
|
364
|
+
reader.seek(offset + 8 + 32);
|
|
365
|
+
const width = reader.uint32le();
|
|
366
|
+
const height = reader.uint32le();
|
|
367
|
+
return width > 0 && height > 0 ? { width, height } : null;
|
|
368
|
+
}
|
|
369
|
+
offset += 8 + size + (size % 2);
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Parses an AMV resource to extract dimensions.
|
|
375
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
376
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
377
|
+
*/
|
|
378
|
+
function parseAmv(reader) {
|
|
379
|
+
const end = reader.limit;
|
|
380
|
+
let offset = 12;
|
|
381
|
+
while (offset + 8 <= end) {
|
|
382
|
+
reader.seek(offset);
|
|
383
|
+
const id = reader.ascii(4);
|
|
384
|
+
const size = reader.uint32le();
|
|
385
|
+
if (id === 'amvh') {
|
|
386
|
+
reader.seek(offset + 8 + 32);
|
|
387
|
+
const width = reader.uint32le();
|
|
388
|
+
const height = reader.uint32le();
|
|
389
|
+
return width > 0 && height > 0 ? { width, height } : null;
|
|
390
|
+
}
|
|
391
|
+
offset += 8 + size + (size % 2);
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
/* -------------------------------------------------------------------------- */
|
|
396
|
+
/* Ogg / Theora */
|
|
397
|
+
/* -------------------------------------------------------------------------- */
|
|
398
|
+
/**
|
|
399
|
+
* @param {TinyBufferReader} reader - The cursor.
|
|
400
|
+
* @returns {MediaProbeSize|null} The dimensions.
|
|
401
|
+
*/
|
|
402
|
+
function parseOgg(reader) {
|
|
403
|
+
reader.seek(26);
|
|
404
|
+
const segments = reader.uint8();
|
|
405
|
+
reader.skip(segments);
|
|
406
|
+
const payload = reader.offset;
|
|
407
|
+
reader.seek(payload);
|
|
408
|
+
if (reader.ascii(7) !== '\x80theora') {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
reader.seek(payload + 10);
|
|
412
|
+
const width = reader.uint24();
|
|
413
|
+
const height = reader.uint24();
|
|
414
|
+
return width > 0 && height > 0 ? { width, height } : null;
|
|
415
|
+
}
|
|
416
|
+
/* -------------------------------------------------------------------------- */
|
|
417
|
+
/* MPEG-1/2 elementary stream */
|
|
418
|
+
/* -------------------------------------------------------------------------- */
|
|
419
|
+
/**
|
|
420
|
+
* Parses an MPEG-1 or MPEG-2 elementary stream to extract dimensions.
|
|
421
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
422
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
423
|
+
*/
|
|
424
|
+
function parseMpeg(reader) {
|
|
425
|
+
const end = reader.limit;
|
|
426
|
+
for (let offset = 0; offset + 12 <= end; offset += 1) {
|
|
427
|
+
reader.seek(offset);
|
|
428
|
+
if (reader.uint24() !== 0x000001 || reader.uint8() !== 0xb3) {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
const a = reader.uint8();
|
|
432
|
+
const b = reader.uint8();
|
|
433
|
+
const c = reader.uint8();
|
|
434
|
+
const width = (a << 4) | (b >> 4);
|
|
435
|
+
const height = ((b & 0x0f) << 8) | c;
|
|
436
|
+
if (width > 0 && height > 0) {
|
|
437
|
+
return { width, height };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
/* -------------------------------------------------------------------------- */
|
|
443
|
+
/* FLV (FLV, F4V, F4P, F4A, F4B) */
|
|
444
|
+
/* -------------------------------------------------------------------------- */
|
|
445
|
+
/**
|
|
446
|
+
* Parses a Flash Video (FLV) resource to extract dimensions.
|
|
447
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
448
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
449
|
+
*/
|
|
450
|
+
function parseFlv(reader) {
|
|
451
|
+
reader.seek(5);
|
|
452
|
+
let offset = reader.uint32();
|
|
453
|
+
const end = reader.limit;
|
|
454
|
+
while (offset + 11 <= end) {
|
|
455
|
+
reader.seek(offset);
|
|
456
|
+
const tagType = reader.uint8();
|
|
457
|
+
const dataSize = reader.uint24();
|
|
458
|
+
reader.skip(7);
|
|
459
|
+
const dataStart = reader.offset;
|
|
460
|
+
if (tagType === 9) {
|
|
461
|
+
const size = readFlvVideo(reader, dataStart, dataStart + dataSize);
|
|
462
|
+
if (size) {
|
|
463
|
+
return size;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
offset = dataStart + dataSize + 4;
|
|
467
|
+
}
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Parses the video tag payload of an FLV stream to extract dimensions.
|
|
472
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
473
|
+
* @param {number} start - The absolute offset of the first byte of the video tag payload.
|
|
474
|
+
* @param {number} end - The absolute offset marking the end of the video tag payload.
|
|
475
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
476
|
+
*/
|
|
477
|
+
function readFlvVideo(reader, start, end) {
|
|
478
|
+
if (start + 16 > end) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
reader.seek(start);
|
|
482
|
+
if ((reader.uint8() & 0x0f) !== 7) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
if (reader.uint8() !== 0) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
reader.skip(5);
|
|
489
|
+
if ((reader.uint8() & 0x1f) === 0) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
const spsLength = reader.uint16();
|
|
493
|
+
try {
|
|
494
|
+
return readH264Sps(reader.bytes(spsLength));
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/* -------------------------------------------------------------------------- */
|
|
501
|
+
/* MPEG program stream (VOB) */
|
|
502
|
+
/* -------------------------------------------------------------------------- */
|
|
503
|
+
/**
|
|
504
|
+
* Parses an MPEG Program Stream (VOB) to extract dimensions.
|
|
505
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
506
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
507
|
+
*/
|
|
508
|
+
function parseMpegPs(reader) {
|
|
509
|
+
const end = reader.limit;
|
|
510
|
+
for (let offset = 0; offset + 12 <= end; offset += 1) {
|
|
511
|
+
reader.seek(offset);
|
|
512
|
+
if (reader.uint24() !== 0x000001 || reader.uint8() !== 0xb3) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
const a = reader.uint8();
|
|
516
|
+
const b = reader.uint8();
|
|
517
|
+
const c = reader.uint8();
|
|
518
|
+
const width = (a << 4) | (b >> 4);
|
|
519
|
+
const height = ((b & 0x0f) << 8) | c;
|
|
520
|
+
if (width > 0 && height > 0) {
|
|
521
|
+
return { width, height };
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
/* -------------------------------------------------------------------------- */
|
|
527
|
+
/* MPEG transport stream (TS, MTS, M2TS) */
|
|
528
|
+
/* -------------------------------------------------------------------------- */
|
|
529
|
+
/**
|
|
530
|
+
* Parses an MPEG Transport Stream (TS) to extract dimensions.
|
|
531
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
532
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
533
|
+
*/
|
|
534
|
+
function parseMpegTs(reader) {
|
|
535
|
+
const end = reader.limit;
|
|
536
|
+
const base = findTsSync(reader, end);
|
|
537
|
+
if (base === -1) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
const pmtPid = findTsPmtPid(reader, base, end);
|
|
541
|
+
if (pmtPid === -1) {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
const videoPid = findTsVideoPid(reader, base, end, pmtPid);
|
|
545
|
+
if (videoPid === -1) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
return findTsSps(reader, base, end, videoPid);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Searches for the first MPEG-TS synchronization byte (0x47) in the buffer.
|
|
552
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
553
|
+
* @param {number} end - The absolute offset marking the end of the buffer.
|
|
554
|
+
* @returns {number} The absolute offset of the first synchronization byte, or -1 if not found.
|
|
555
|
+
*/
|
|
556
|
+
function findTsSync(reader, end) {
|
|
557
|
+
for (let offset = 0; offset + 376 < Math.min(end, 8192); offset += 1) {
|
|
558
|
+
if (reader.seek(offset).uint8() !== 0x47) {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (reader.seek(offset + 188).uint8() === 0x47 && reader.seek(offset + 376).uint8() === 0x47) {
|
|
562
|
+
return offset;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return -1;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Searches for the Program Map Table (PMT) PID within an MPEG-TS stream.
|
|
569
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
570
|
+
* @param {number} base - The absolute offset of the first synchronization byte.
|
|
571
|
+
* @param {number} end - The absolute offset marking the end of the buffer.
|
|
572
|
+
* @returns {number} The PMT PID, or -1 if not found.
|
|
573
|
+
*/
|
|
574
|
+
function findTsPmtPid(reader, base, end) {
|
|
575
|
+
for (let offset = base; offset + 188 <= end; offset += 188) {
|
|
576
|
+
reader.seek(offset);
|
|
577
|
+
if (reader.uint8() !== 0x47) {
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const flags = reader.uint8();
|
|
581
|
+
const pid = ((flags & 0x1f) << 8) | reader.uint8();
|
|
582
|
+
if (pid !== 0) {
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
reader.skip(1);
|
|
586
|
+
const pointer = reader.uint8();
|
|
587
|
+
reader.skip(pointer);
|
|
588
|
+
if (reader.uint8() !== 0x00) {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
reader.skip(2);
|
|
592
|
+
reader.skip(3);
|
|
593
|
+
const programCount = (reader.uint8() << 8) | reader.uint8();
|
|
594
|
+
for (let i = 0; i < programCount; i += 1) {
|
|
595
|
+
reader.skip(2);
|
|
596
|
+
const pmtPid = ((reader.uint8() & 0x1f) << 8) | reader.uint8();
|
|
597
|
+
return pmtPid;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return -1;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Searches for the elementary video stream PID using the PMT.
|
|
604
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
605
|
+
* @param {number} base - The absolute offset of the first synchronization byte.
|
|
606
|
+
* @param {number} end - The absolute offset marking the end of the buffer.
|
|
607
|
+
* @param {number} pmtPid - The PMT PID found previously.
|
|
608
|
+
* @returns {number} The elementary video PID, or -1 if not found.
|
|
609
|
+
*/
|
|
610
|
+
function findTsVideoPid(reader, base, end, pmtPid) {
|
|
611
|
+
for (let offset = base; offset + 188 <= end; offset += 188) {
|
|
612
|
+
reader.seek(offset);
|
|
613
|
+
if (reader.uint8() !== 0x47) {
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
const flags = reader.uint8();
|
|
617
|
+
const pid = ((flags & 0x1f) << 8) | reader.uint8();
|
|
618
|
+
if (pid !== pmtPid) {
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
reader.skip(1);
|
|
622
|
+
const pointer = reader.uint8();
|
|
623
|
+
reader.skip(pointer);
|
|
624
|
+
if (reader.uint8() !== 0x02) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
const sectionLength = reader.uint16() & 0x03ff;
|
|
628
|
+
const sectionEnd = reader.offset + sectionLength - 4;
|
|
629
|
+
reader.skip(5);
|
|
630
|
+
while (reader.offset + 5 <= sectionEnd) {
|
|
631
|
+
const streamType = reader.uint8();
|
|
632
|
+
const elementaryPid = reader.uint16() & 0x1fff;
|
|
633
|
+
reader.skip(2);
|
|
634
|
+
if (streamType === 0x1b || streamType === 0x02) {
|
|
635
|
+
return elementaryPid;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return -1;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Parses the Sequence Parameter Set (SPS) of an MPEG-TS video stream to extract dimensions.
|
|
643
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
644
|
+
* @param {number} base - The absolute offset of the first synchronization byte.
|
|
645
|
+
* @param {number} end - The absolute offset marking the end of the buffer.
|
|
646
|
+
* @param {number} pid - The elementary video PID.
|
|
647
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
648
|
+
*/
|
|
649
|
+
function findTsSps(reader, base, end, pid) {
|
|
650
|
+
const payload = [];
|
|
651
|
+
for (let offset = base; offset + 188 <= end; offset += 188) {
|
|
652
|
+
reader.seek(offset);
|
|
653
|
+
if (reader.uint8() !== 0x47) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
const flags = reader.uint8();
|
|
657
|
+
const packetPid = ((flags & 0x1f) << 8) | reader.uint8();
|
|
658
|
+
if (packetPid !== pid) {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
const start = reader.offset + 1 + reader.buffer[reader.offset];
|
|
662
|
+
for (let i = start; i < offset + 188; i += 1) {
|
|
663
|
+
payload.push(reader.buffer[i]);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
for (let i = 0; i + 4 < payload.length; i += 1) {
|
|
667
|
+
if (payload[i] === 0 &&
|
|
668
|
+
payload[i + 1] === 0 &&
|
|
669
|
+
payload[i + 2] === 1 &&
|
|
670
|
+
(payload[i + 3] & 0x1f) === 7) {
|
|
671
|
+
try {
|
|
672
|
+
return readH264Sps(Uint8Array.from(payload.slice(i + 3)));
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
/* -------------------------------------------------------------------------- */
|
|
682
|
+
/* ASF (WMV, ASF) */
|
|
683
|
+
/* -------------------------------------------------------------------------- */
|
|
684
|
+
/**
|
|
685
|
+
* Parses an ASF (Advanced Systems Format) resource to extract dimensions.
|
|
686
|
+
* @param {TinyBufferReader} reader - The binary cursor used for reading.
|
|
687
|
+
* @returns {MediaProbeSize|null} The extracted dimensions, or null if not found.
|
|
688
|
+
*/
|
|
689
|
+
function parseAsf(reader) {
|
|
690
|
+
const end = reader.limit;
|
|
691
|
+
const guid = [
|
|
692
|
+
0xc0, 0xef, 0x19, 0xbc, 0x4d, 0x4b, 0xcf, 0x11, 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b,
|
|
693
|
+
];
|
|
694
|
+
for (let offset = 0; offset + 16 <= end; offset += 1) {
|
|
695
|
+
reader.seek(offset);
|
|
696
|
+
if (!guid.every((byte) => reader.uint8() === byte)) {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
reader.skip(24);
|
|
700
|
+
const width = reader.uint32le();
|
|
701
|
+
const height = reader.uint32le();
|
|
702
|
+
if (width > 0 && height > 0 && width < 65536 && height < 65536) {
|
|
703
|
+
return { width, height };
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
/* -------------------------------------------------------------------------- */
|
|
709
|
+
/* RealMedia (RM, RMVB) */
|
|
710
|
+
/* -------------------------------------------------------------------------- */
|
|
711
|
+
/**
|
|
712
|
+
* @param {TinyBufferReader} reader - The cursor.
|
|
713
|
+
* @returns {MediaProbeSize|null} The dimensions.
|
|
714
|
+
*/
|
|
715
|
+
function parseRealMedia(reader) {
|
|
716
|
+
const end = reader.limit;
|
|
717
|
+
for (let offset = 0; offset + 12 <= end; offset += 1) {
|
|
718
|
+
reader.seek(offset);
|
|
719
|
+
if (reader.ascii(4) !== 'VIDO') {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
reader.skip(6);
|
|
723
|
+
const width = reader.uint16();
|
|
724
|
+
const height = reader.uint16();
|
|
725
|
+
if (width > 0 && height > 0 && width < 65536 && height < 65536) {
|
|
726
|
+
return { width, height };
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
/* -------------------------------------------------------------------------- */
|
|
732
|
+
/* NSV */
|
|
733
|
+
/* -------------------------------------------------------------------------- */
|
|
734
|
+
/**
|
|
735
|
+
* @param {TinyBufferReader} reader - The cursor.
|
|
736
|
+
* @returns {MediaProbeSize|null} The dimensions.
|
|
737
|
+
*/
|
|
738
|
+
function parseNsv(reader) {
|
|
739
|
+
const end = reader.limit;
|
|
740
|
+
for (let offset = 0; offset + 16 <= end; offset += 1) {
|
|
741
|
+
reader.seek(offset);
|
|
742
|
+
if (reader.ascii(4) !== 'NSVs') {
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
reader.skip(4);
|
|
746
|
+
const width = reader.uint16le();
|
|
747
|
+
const height = reader.uint16le();
|
|
748
|
+
if (width > 0 && height > 0) {
|
|
749
|
+
return { width, height };
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
/* -------------------------------------------------------------------------- */
|
|
755
|
+
/* RoQ */
|
|
756
|
+
/* -------------------------------------------------------------------------- */
|
|
757
|
+
/**
|
|
758
|
+
* @param {TinyBufferReader} reader - The cursor.
|
|
759
|
+
* @returns {MediaProbeSize|null} The dimensions.
|
|
760
|
+
*/
|
|
761
|
+
function parseRoq(reader) {
|
|
762
|
+
reader.seek(8);
|
|
763
|
+
const width = reader.uint16le();
|
|
764
|
+
const height = reader.uint16le();
|
|
765
|
+
return width > 0 && height > 0 ? { width, height } : null;
|
|
766
|
+
}
|
|
767
|
+
/* -------------------------------------------------------------------------- */
|
|
768
|
+
/* Registry */
|
|
769
|
+
/* -------------------------------------------------------------------------- */
|
|
770
|
+
/**
|
|
771
|
+
* The container registry. The order matters: the first signature that matches wins.
|
|
772
|
+
* @type {ReadonlyArray<MediaFormat>}
|
|
773
|
+
*/
|
|
774
|
+
const FORMATS = Object.freeze([
|
|
775
|
+
{ name: 'iso-bmff', sniff: (b) => matches(b, [0x66, 0x74, 0x79, 0x70], 4), parse: parseIsoBmff },
|
|
776
|
+
{ name: 'matroska', sniff: (b) => matches(b, [0x1a, 0x45, 0xdf, 0xa3]), parse: parseWebm },
|
|
777
|
+
{
|
|
778
|
+
name: 'amv',
|
|
779
|
+
sniff: (b) => matches(b, [0x52, 0x49, 0x46, 0x46]) && matches(b, [0x41, 0x4d, 0x56, 0x20], 8),
|
|
780
|
+
parse: parseAmv,
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
name: 'avi',
|
|
784
|
+
sniff: (b) => matches(b, [0x52, 0x49, 0x46, 0x46]) && matches(b, [0x41, 0x56, 0x49, 0x20], 8),
|
|
785
|
+
parse: parseAvi,
|
|
786
|
+
},
|
|
787
|
+
{ name: 'ogg', sniff: (b) => matches(b, [0x4f, 0x67, 0x67, 0x53]), parse: parseOgg },
|
|
788
|
+
{ name: 'flv', sniff: (b) => matches(b, [0x46, 0x4c, 0x56, 0x01]), parse: parseFlv },
|
|
789
|
+
{
|
|
790
|
+
name: 'asf',
|
|
791
|
+
sniff: (b) => matches(b, [0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11]),
|
|
792
|
+
parse: parseAsf,
|
|
793
|
+
},
|
|
794
|
+
{ name: 'realmedia', sniff: (b) => matches(b, [0x2e, 0x52, 0x4d, 0x46]), parse: parseRealMedia },
|
|
795
|
+
{ name: 'nsv', sniff: (b) => matches(b, [0x4e, 0x53, 0x56, 0x66]), parse: parseNsv },
|
|
796
|
+
{ name: 'roq', sniff: (b) => matches(b, [0x84, 0x10, 0xff, 0xff]), parse: parseRoq },
|
|
797
|
+
{
|
|
798
|
+
name: 'mpeg-ts',
|
|
799
|
+
sniff: (b) => b[0] === 0x47 && b[188] === 0x47 && b[376] === 0x47,
|
|
800
|
+
parse: parseMpegTs,
|
|
801
|
+
},
|
|
802
|
+
{ name: 'mpeg-ps', sniff: (b) => matches(b, [0x00, 0x00, 0x01, 0xba]), parse: parseMpegPs },
|
|
803
|
+
{ name: 'mpeg', sniff: (b) => matches(b, [0x00, 0x00, 0x01, 0xb3]), parse: parseMpeg },
|
|
804
|
+
]);
|
|
805
|
+
/**
|
|
806
|
+
* Reads the intrinsic dimensions of a video stream from its container header.
|
|
807
|
+
* @beta
|
|
808
|
+
*/
|
|
809
|
+
class TinyMediaProbe {
|
|
810
|
+
/** @type {Uint8Array} */
|
|
811
|
+
#buffer = new Uint8Array(0);
|
|
812
|
+
/** @type {MediaFormat|null} */
|
|
813
|
+
#format = null;
|
|
814
|
+
/** @type {boolean} */
|
|
815
|
+
#resolved = false;
|
|
816
|
+
/** @type {boolean} */
|
|
817
|
+
#abandoned = false;
|
|
818
|
+
/** @type {number} */
|
|
819
|
+
#maxBytes;
|
|
820
|
+
/**
|
|
821
|
+
* @param {string} [type] - The MIME type from the response headers. Unused, kept for API compatibility.
|
|
822
|
+
* @param {number} [maxBytes] - The maximum number of header bytes to buffer.
|
|
823
|
+
* @throws {RangeError} If `maxBytes` is not a positive number.
|
|
824
|
+
*/
|
|
825
|
+
constructor(type = '', maxBytes = MAX_HEADER_BYTES) {
|
|
826
|
+
if (typeof maxBytes !== 'number' || Number.isNaN(maxBytes) || maxBytes < 1) {
|
|
827
|
+
throw new RangeError('The "maxBytes" argument must be a positive number.');
|
|
828
|
+
}
|
|
829
|
+
this.#maxBytes = maxBytes;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Appends a chunk and returns the dimensions once they are known.
|
|
833
|
+
* @param {Uint8Array} chunk - The bytes received so far.
|
|
834
|
+
* @returns {MediaProbeSize|null} The dimensions, or null when more data is needed.
|
|
835
|
+
* @throws {TypeError} If `chunk` is not a Uint8Array.
|
|
836
|
+
*/
|
|
837
|
+
push(chunk) {
|
|
838
|
+
if (!(chunk instanceof Uint8Array)) {
|
|
839
|
+
throw new TypeError('The "chunk" argument must be a Uint8Array.');
|
|
840
|
+
}
|
|
841
|
+
if (this.#resolved || this.#abandoned) {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
const merged = new Uint8Array(this.#buffer.length + chunk.length);
|
|
845
|
+
merged.set(this.#buffer);
|
|
846
|
+
merged.set(chunk, this.#buffer.length);
|
|
847
|
+
this.#buffer = merged;
|
|
848
|
+
if (!this.#format) {
|
|
849
|
+
this.#format = this.#detect();
|
|
850
|
+
if (!this.#format) {
|
|
851
|
+
if (this.#buffer.length > 512) {
|
|
852
|
+
this.#abandoned = true;
|
|
853
|
+
}
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
try {
|
|
858
|
+
const size = this.#format.parse(new TinyBufferReader(this.#buffer));
|
|
859
|
+
if (size) {
|
|
860
|
+
this.#resolved = true;
|
|
861
|
+
this.#buffer = new Uint8Array(0);
|
|
862
|
+
return size;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
catch (error) {
|
|
866
|
+
if (!(error instanceof TinyNeedMoreData)) {
|
|
867
|
+
throw error;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (this.#buffer.length > this.#maxBytes) {
|
|
871
|
+
this.#abandoned = true;
|
|
872
|
+
this.#buffer = new Uint8Array(0);
|
|
873
|
+
}
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Releases the buffered header.
|
|
878
|
+
* @returns {void}
|
|
879
|
+
*/
|
|
880
|
+
close() {
|
|
881
|
+
this.#buffer = new Uint8Array(0);
|
|
882
|
+
this.#abandoned = true;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* @returns {MediaFormat|null} The detected format, or null when more data is needed.
|
|
886
|
+
*/
|
|
887
|
+
#detect() {
|
|
888
|
+
for (const format of FORMATS) {
|
|
889
|
+
if (format.sniff(this.#buffer)) {
|
|
890
|
+
return format;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
export default TinyMediaProbe;
|