@aglyn/aglyn 1.0.0-beta.197 → 1.0.0-beta.199
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/package.json +11 -11
- package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
- package/src/lib/app-utils/docs-help.generated.js +1 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +3 -3
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/media-embedded-fields.d.ts +471 -0
- package/src/lib/app-utils/media-embedded-fields.js +619 -0
- package/src/lib/app-utils/media-embedded-fields.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/exif.d.ts +160 -0
- package/src/lib/app-utils/media-embedded-metadata/exif.js +1267 -0
- package/src/lib/app-utils/media-embedded-metadata/exif.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/gif.d.ts +44 -0
- package/src/lib/app-utils/media-embedded-metadata/gif.js +134 -0
- package/src/lib/app-utils/media-embedded-metadata/gif.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/heif.d.ts +23 -0
- package/src/lib/app-utils/media-embedded-metadata/heif.js +354 -0
- package/src/lib/app-utils/media-embedded-metadata/heif.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/icc.d.ts +22 -0
- package/src/lib/app-utils/media-embedded-metadata/icc.js +85 -0
- package/src/lib/app-utils/media-embedded-metadata/icc.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/image-blocks.d.ts +145 -0
- package/src/lib/app-utils/media-embedded-metadata/image-blocks.js +200 -0
- package/src/lib/app-utils/media-embedded-metadata/image-blocks.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/index.d.ts +93 -0
- package/src/lib/app-utils/media-embedded-metadata/index.js +744 -0
- package/src/lib/app-utils/media-embedded-metadata/index.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/iptc.d.ts +59 -0
- package/src/lib/app-utils/media-embedded-metadata/iptc.js +529 -0
- package/src/lib/app-utils/media-embedded-metadata/iptc.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/jpeg.d.ts +43 -0
- package/src/lib/app-utils/media-embedded-metadata/jpeg.js +456 -0
- package/src/lib/app-utils/media-embedded-metadata/jpeg.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/mp4.d.ts +37 -0
- package/src/lib/app-utils/media-embedded-metadata/mp4.js +621 -0
- package/src/lib/app-utils/media-embedded-metadata/mp4.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/ooxml.d.ts +131 -0
- package/src/lib/app-utils/media-embedded-metadata/ooxml.js +1058 -0
- package/src/lib/app-utils/media-embedded-metadata/ooxml.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf-objects.d.ts +223 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js +936 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf.d.ts +82 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf.js +1167 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/png.d.ts +41 -0
- package/src/lib/app-utils/media-embedded-metadata/png.js +512 -0
- package/src/lib/app-utils/media-embedded-metadata/png.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/svg.d.ts +30 -0
- package/src/lib/app-utils/media-embedded-metadata/svg.js +154 -0
- package/src/lib/app-utils/media-embedded-metadata/svg.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/tiff.d.ts +40 -0
- package/src/lib/app-utils/media-embedded-metadata/tiff.js +158 -0
- package/src/lib/app-utils/media-embedded-metadata/tiff.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/types.d.ts +60 -0
- package/src/lib/app-utils/media-embedded-metadata/types.js +32 -0
- package/src/lib/app-utils/media-embedded-metadata/types.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/webm.d.ts +26 -0
- package/src/lib/app-utils/media-embedded-metadata/webm.js +417 -0
- package/src/lib/app-utils/media-embedded-metadata/webm.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/webp.d.ts +37 -0
- package/src/lib/app-utils/media-embedded-metadata/webp.js +272 -0
- package/src/lib/app-utils/media-embedded-metadata/webp.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/xml.d.ts +231 -0
- package/src/lib/app-utils/media-embedded-metadata/xml.js +598 -0
- package/src/lib/app-utils/media-embedded-metadata/xml.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/xmp.d.ts +100 -0
- package/src/lib/app-utils/media-embedded-metadata/xmp.js +1475 -0
- package/src/lib/app-utils/media-embedded-metadata/xmp.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/zip.d.ts +93 -0
- package/src/lib/app-utils/media-embedded-metadata/zip.js +531 -0
- package/src/lib/app-utils/media-embedded-metadata/zip.js.map +1 -0
- package/src/lib/app-utils/name-search.d.ts +12 -0
- package/src/lib/app-utils/name-search.js +11 -0
- package/src/lib/app-utils/name-search.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +11 -0
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/server.d.ts +1 -0
- package/src/server.js +7 -0
- package/src/server.js.map +1 -1
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The tags of a WebM or Matroska film (AGL-3331). Read-only, like MP4.
|
|
18
|
+
*
|
|
19
|
+
* Matroska is EBML (RFC 8794): every element is a variable-length ID, a
|
|
20
|
+
* variable-length size and a payload. The file is an EBML header naming the
|
|
21
|
+
* DocType, then one `Segment` whose top-level children include `Info`
|
|
22
|
+
* (title, date, muxer) and `Tags`, and — the bulk of the file — `Cluster`s
|
|
23
|
+
* of media (RFC 9559 §5.1).
|
|
24
|
+
*
|
|
25
|
+
* ## Reading without downloading the film
|
|
26
|
+
*
|
|
27
|
+
* Every read goes through the random-access reader and is sized exactly: an
|
|
28
|
+
* element header is read in at most two pieces (see `headerAt`), so even the
|
|
29
|
+
* header of a `Cluster` costs its own few bytes and nothing of its payload.
|
|
30
|
+
* The Segment's children are walked from the start until the first
|
|
31
|
+
* `Cluster`; anything that lives past the media — `Tags` usually does,
|
|
32
|
+
* because a muxer writes them last — is reached through the `SeekHead`
|
|
33
|
+
* index (RFC 9559 §5.1.1), whose positions count from the start of the
|
|
34
|
+
* Segment's payload. A Segment of unknown size (a live recording, which is
|
|
35
|
+
* what a browser's MediaRecorder writes) runs to the end of the file, and a
|
|
36
|
+
* `Cluster` of unknown size simply ends the walk.
|
|
37
|
+
*
|
|
38
|
+
* ## What is read
|
|
39
|
+
*
|
|
40
|
+
* - `Info` (§5.1.2): `Title`, `DateUTC` (signed nanoseconds since
|
|
41
|
+
* 2001-01-01T00:00:00Z), `WritingApp` — or `MuxingApp` when that is all
|
|
42
|
+
* there is — as the encoder.
|
|
43
|
+
* - `Tags` (§5.1.8): the `SimpleTag`s of every `Tag` that targets the whole
|
|
44
|
+
* file — no track, edition, chapter or attachment UID — preferring target
|
|
45
|
+
* level 50, the film itself. A per-track tag (ffmpeg writes a `DURATION`
|
|
46
|
+
* for every track) describes a stream, not the file, and is skipped.
|
|
47
|
+
* Nested SimpleTags are not read.
|
|
48
|
+
*
|
|
49
|
+
* Tags outrank `Info` for the same idea: `DATE_RECORDED` says when the
|
|
50
|
+
* recording began, `DateUTC` only when the file was muxed.
|
|
51
|
+
*/ import { otherEmbeddedKey } from "../media-embedded-fields.js";
|
|
52
|
+
// Element IDs, with their length-marker bits, as the specifications list them.
|
|
53
|
+
const ID_EBML = 0x1a45dfa3;
|
|
54
|
+
const ID_DOCTYPE = 0x4282;
|
|
55
|
+
const ID_SEGMENT = 0x18538067;
|
|
56
|
+
const ID_SEEKHEAD = 0x114d9b74;
|
|
57
|
+
const ID_SEEK = 0x4dbb;
|
|
58
|
+
const ID_SEEKID = 0x53ab;
|
|
59
|
+
const ID_SEEKPOSITION = 0x53ac;
|
|
60
|
+
const ID_INFO = 0x1549a966;
|
|
61
|
+
const ID_TITLE = 0x7ba9;
|
|
62
|
+
const ID_DATEUTC = 0x4461;
|
|
63
|
+
const ID_MUXINGAPP = 0x4d80;
|
|
64
|
+
const ID_WRITINGAPP = 0x5741;
|
|
65
|
+
const ID_TAGS = 0x1254c367;
|
|
66
|
+
const ID_TAG = 0x7373;
|
|
67
|
+
const ID_TARGETS = 0x63c0;
|
|
68
|
+
const ID_TARGETTYPEVALUE = 0x68ca;
|
|
69
|
+
const ID_TAG_UIDS = new Set([
|
|
70
|
+
0x63c5,
|
|
71
|
+
0x63c9,
|
|
72
|
+
0x63c4,
|
|
73
|
+
0x63c6
|
|
74
|
+
]);
|
|
75
|
+
const ID_SIMPLETAG = 0x67c8;
|
|
76
|
+
const ID_TAGNAME = 0x45a3;
|
|
77
|
+
const ID_TAGSTRING = 0x4487;
|
|
78
|
+
const ID_CLUSTER = 0x1f43b675;
|
|
79
|
+
const EBML_HEADER_MAX_BYTES = 4096;
|
|
80
|
+
const SEEKHEAD_MAX_BYTES = 1024 * 1024;
|
|
81
|
+
const INFO_MAX_BYTES = 1024 * 1024;
|
|
82
|
+
const TAGS_MAX_BYTES = 8 * 1024 * 1024;
|
|
83
|
+
/** Top-level elements ahead of the first Cluster: a handful, in practice. */ const MAX_TOP_LEVEL = 256;
|
|
84
|
+
const MAX_CHILDREN = 65536;
|
|
85
|
+
/** Milliseconds from 1970 to 2001-01-01T00:00:00Z, Matroska's epoch. */ const EPOCH_2001_MS = 978307200000;
|
|
86
|
+
/** The length of a VINT from its first byte's leading zeros (RFC 8794 §4). */ function vintLength(first) {
|
|
87
|
+
for(let length = 1, mask = 0x80; length <= 8; length++, mask >>= 1){
|
|
88
|
+
if (first & mask) return length;
|
|
89
|
+
}
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
/** An element ID: 1 to 4 bytes, marker bit kept (RFC 8794 §5). */ function readId(bytes, at) {
|
|
93
|
+
var _bytes_at, _bytes_;
|
|
94
|
+
const length = vintLength((_bytes_at = bytes[at]) != null ? _bytes_at : 0);
|
|
95
|
+
if (!length || length > 4 || at + length > bytes.length) return null;
|
|
96
|
+
let id = 0;
|
|
97
|
+
for(let index = 0; index < length; index++)id = id * 256 + ((_bytes_ = bytes[at + index]) != null ? _bytes_ : 0);
|
|
98
|
+
return {
|
|
99
|
+
id,
|
|
100
|
+
length
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* An element data size: 1 to 8 bytes, marker bit dropped; every value bit
|
|
105
|
+
* set means "unknown" (RFC 8794 §6.2). A size past 2^53 is refused.
|
|
106
|
+
*/ function readSize(bytes, at) {
|
|
107
|
+
var _bytes_at;
|
|
108
|
+
const first = (_bytes_at = bytes[at]) != null ? _bytes_at : 0;
|
|
109
|
+
const length = vintLength(first);
|
|
110
|
+
if (!length || at + length > bytes.length) return null;
|
|
111
|
+
let value = first & 0xff >> length;
|
|
112
|
+
let allOnes = value === 0xff >> length;
|
|
113
|
+
for(let index = 1; index < length; index++){
|
|
114
|
+
var _bytes_;
|
|
115
|
+
const byte = (_bytes_ = bytes[at + index]) != null ? _bytes_ : 0;
|
|
116
|
+
value = value * 256 + byte;
|
|
117
|
+
if (byte !== 0xff) allOnes = false;
|
|
118
|
+
}
|
|
119
|
+
if (allOnes) return {
|
|
120
|
+
size: null,
|
|
121
|
+
length
|
|
122
|
+
};
|
|
123
|
+
return Number.isSafeInteger(value) ? {
|
|
124
|
+
size: value,
|
|
125
|
+
length
|
|
126
|
+
} : null;
|
|
127
|
+
}
|
|
128
|
+
/** The elements inside `bytes[start, end)`, clamped to it. */ function elements(bytes, start, end) {
|
|
129
|
+
const out = [];
|
|
130
|
+
let at = start;
|
|
131
|
+
while(at < end && out.length < MAX_CHILDREN){
|
|
132
|
+
const id = readId(bytes, at);
|
|
133
|
+
if (!id) break;
|
|
134
|
+
const size = readSize(bytes, at + id.length);
|
|
135
|
+
if (!size) break;
|
|
136
|
+
const body = at + id.length + size.length;
|
|
137
|
+
if (body > end) break;
|
|
138
|
+
// An unknown size inside a buffer can only run to the buffer's end.
|
|
139
|
+
const length = size.size === null ? end - body : Math.min(size.size, end - body);
|
|
140
|
+
out.push({
|
|
141
|
+
id: id.id,
|
|
142
|
+
start: at,
|
|
143
|
+
body,
|
|
144
|
+
size: length
|
|
145
|
+
});
|
|
146
|
+
at = body + length;
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
const payload = (bytes, element)=>{
|
|
151
|
+
var _element_size;
|
|
152
|
+
return bytes.subarray(element.body, element.body + ((_element_size = element.size) != null ? _element_size : 0));
|
|
153
|
+
};
|
|
154
|
+
/** A Matroska string: UTF-8, possibly zero-padded (RFC 8794 §7.4, §7.5). */ const text = (bytes)=>new TextDecoder('utf-8').decode(bytes).replace(/\0+$/, '');
|
|
155
|
+
function unsigned(bytes) {
|
|
156
|
+
if (bytes.length > 8) return null;
|
|
157
|
+
let value = 0;
|
|
158
|
+
for (const byte of bytes)value = value * 256 + byte;
|
|
159
|
+
return Number.isSafeInteger(value) ? value : null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* One element header read through the reader in at most two sized pieces:
|
|
163
|
+
* five bytes — a 4-byte ID and the first byte of the size, which says how
|
|
164
|
+
* long the size is — then whatever of the size is still missing. For a
|
|
165
|
+
* `Cluster` (a 4-byte ID) the first read is all header; for a shorter ID it
|
|
166
|
+
* may run into that element's own payload, never into a Cluster's — so no
|
|
167
|
+
* read reaches into the media this module means to skip.
|
|
168
|
+
*/ async function headerAt(reader, at) {
|
|
169
|
+
var _held_;
|
|
170
|
+
if (at < 0 || at >= reader.size) return null;
|
|
171
|
+
const head = await reader.read(at, Math.min(reader.size, at + 5));
|
|
172
|
+
const id = readId(head, 0);
|
|
173
|
+
if (!id) return null;
|
|
174
|
+
const sizeAt = at + id.length;
|
|
175
|
+
const held = head.subarray(id.length);
|
|
176
|
+
const length = vintLength((_held_ = held[0]) != null ? _held_ : 0);
|
|
177
|
+
if (!held.length || !length) return null;
|
|
178
|
+
let sizeBytes = held.subarray(0, length);
|
|
179
|
+
if (held.length < length) {
|
|
180
|
+
const rest = await reader.read(sizeAt + held.length, sizeAt + length);
|
|
181
|
+
if (held.length + rest.length < length) return null;
|
|
182
|
+
sizeBytes = new Uint8Array(length);
|
|
183
|
+
sizeBytes.set(held);
|
|
184
|
+
sizeBytes.set(rest, held.length);
|
|
185
|
+
}
|
|
186
|
+
const size = readSize(sizeBytes, 0);
|
|
187
|
+
if (!size) return null;
|
|
188
|
+
return {
|
|
189
|
+
id: id.id,
|
|
190
|
+
start: at,
|
|
191
|
+
body: sizeAt + length,
|
|
192
|
+
size: size.size
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/** Official tag names (Matroska Media Container Tag Specifications). */ const TAG_KEYS = {
|
|
196
|
+
TITLE: 'title',
|
|
197
|
+
ARTIST: 'creator',
|
|
198
|
+
DESCRIPTION: 'description',
|
|
199
|
+
COMMENT: 'comment',
|
|
200
|
+
DATE_RECORDED: 'createdAt',
|
|
201
|
+
COPYRIGHT: 'copyright',
|
|
202
|
+
KEYWORDS: 'keywords',
|
|
203
|
+
ENCODER: 'encoder'
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* A tag date — `YYYY-MM-DD hh:mm:ss.mss`, any tail of it omitted — in the
|
|
207
|
+
* catalog's form. The tag specification names no zone, so none is added.
|
|
208
|
+
*/ function normalizeTagDate(raw) {
|
|
209
|
+
const value = raw.trim();
|
|
210
|
+
const match = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?)(?:[.,](\d+))?(Z|[+-]\d{2}:\d{2})?$/.exec(value);
|
|
211
|
+
if (!match) return value;
|
|
212
|
+
const [, date, time, fraction, zone] = match;
|
|
213
|
+
return `${date}T${time}${fraction ? `.${fraction.slice(0, 3)}` : ''}${zone != null ? zone : ''}`;
|
|
214
|
+
}
|
|
215
|
+
let Collector = class Collector {
|
|
216
|
+
add(key, value, rank, label) {
|
|
217
|
+
const trimmed = value.trim();
|
|
218
|
+
if (!trimmed) return;
|
|
219
|
+
let candidate;
|
|
220
|
+
if (key === 'keywords') {
|
|
221
|
+
const items = trimmed.split(/[,;]/).map((item)=>item.trim()).filter(Boolean);
|
|
222
|
+
if (!items.length) return;
|
|
223
|
+
candidate = {
|
|
224
|
+
key,
|
|
225
|
+
value: items,
|
|
226
|
+
source: 'webm',
|
|
227
|
+
editable: false
|
|
228
|
+
};
|
|
229
|
+
} else if (key === 'creator') {
|
|
230
|
+
candidate = {
|
|
231
|
+
key,
|
|
232
|
+
value: [
|
|
233
|
+
trimmed
|
|
234
|
+
],
|
|
235
|
+
source: 'webm',
|
|
236
|
+
editable: false
|
|
237
|
+
};
|
|
238
|
+
} else if (key === 'createdAt') {
|
|
239
|
+
candidate = {
|
|
240
|
+
key,
|
|
241
|
+
value: normalizeTagDate(trimmed),
|
|
242
|
+
source: 'webm',
|
|
243
|
+
editable: false
|
|
244
|
+
};
|
|
245
|
+
} else {
|
|
246
|
+
candidate = {
|
|
247
|
+
key,
|
|
248
|
+
value: trimmed,
|
|
249
|
+
source: 'webm',
|
|
250
|
+
editable: false
|
|
251
|
+
};
|
|
252
|
+
if (label) candidate.label = label;
|
|
253
|
+
}
|
|
254
|
+
const held = this.byKey.get(key);
|
|
255
|
+
if (!held || rank < held.rank) this.byKey.set(key, {
|
|
256
|
+
candidate,
|
|
257
|
+
rank
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
candidates() {
|
|
261
|
+
return [
|
|
262
|
+
...this.byKey.values()
|
|
263
|
+
].map(({ candidate })=>candidate);
|
|
264
|
+
}
|
|
265
|
+
constructor(){
|
|
266
|
+
this.byKey = new Map();
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
/** Ranks: a film-level tag, another whole-file tag, then Info. */ const RANK_TAG_FILM = 0;
|
|
270
|
+
const RANK_TAG_OTHER = 10;
|
|
271
|
+
const RANK_INFO = 20;
|
|
272
|
+
function readTags(bytes, out) {
|
|
273
|
+
for (const tag of elements(bytes, 0, bytes.length)){
|
|
274
|
+
var _tag_size;
|
|
275
|
+
if (tag.id !== ID_TAG) continue;
|
|
276
|
+
const parts = elements(bytes, tag.body, tag.body + ((_tag_size = tag.size) != null ? _tag_size : 0));
|
|
277
|
+
const targets = parts.find((part)=>part.id === ID_TARGETS);
|
|
278
|
+
let level = 50;
|
|
279
|
+
let wholeFile = true;
|
|
280
|
+
if (targets) {
|
|
281
|
+
var _targets_size;
|
|
282
|
+
for (const target of elements(bytes, targets.body, targets.body + ((_targets_size = targets.size) != null ? _targets_size : 0))){
|
|
283
|
+
const value = unsigned(payload(bytes, target));
|
|
284
|
+
if (target.id === ID_TARGETTYPEVALUE && value !== null) level = value;
|
|
285
|
+
if (ID_TAG_UIDS.has(target.id) && value !== 0) wholeFile = false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (!wholeFile) continue;
|
|
289
|
+
const rank = level === 50 ? RANK_TAG_FILM : RANK_TAG_OTHER;
|
|
290
|
+
for (const simple of parts){
|
|
291
|
+
var _simple_size;
|
|
292
|
+
if (simple.id !== ID_SIMPLETAG) continue;
|
|
293
|
+
let name = '';
|
|
294
|
+
let value = null;
|
|
295
|
+
for (const field of elements(bytes, simple.body, simple.body + ((_simple_size = simple.size) != null ? _simple_size : 0))){
|
|
296
|
+
if (field.id === ID_TAGNAME) name = text(payload(bytes, field)).trim();
|
|
297
|
+
else if (field.id === ID_TAGSTRING) value = text(payload(bytes, field));
|
|
298
|
+
}
|
|
299
|
+
if (!name || value === null) continue;
|
|
300
|
+
const key = TAG_KEYS[name.toUpperCase()];
|
|
301
|
+
if (key) out.add(key, value, rank);
|
|
302
|
+
else out.add(otherEmbeddedKey('webm', name), value, rank, name);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function readInfo(bytes, out) {
|
|
307
|
+
let muxingApp = '';
|
|
308
|
+
let writingApp = '';
|
|
309
|
+
for (const field of elements(bytes, 0, bytes.length)){
|
|
310
|
+
const value = payload(bytes, field);
|
|
311
|
+
if (field.id === ID_TITLE) out.add('title', text(value), RANK_INFO);
|
|
312
|
+
else if (field.id === ID_MUXINGAPP) muxingApp = text(value).trim();
|
|
313
|
+
else if (field.id === ID_WRITINGAPP) writingApp = text(value).trim();
|
|
314
|
+
else if (field.id === ID_DATEUTC && value.length === 8) {
|
|
315
|
+
const nanoseconds = new DataView(value.buffer, value.byteOffset, value.byteLength).getBigInt64(0);
|
|
316
|
+
const date = new Date(EPOCH_2001_MS + Number(nanoseconds / 1000000n));
|
|
317
|
+
if (!Number.isNaN(date.getTime())) {
|
|
318
|
+
out.add('createdAt', date.toISOString().replace(/\.\d{3}Z$/, 'Z'), RANK_INFO);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const encoder = writingApp || muxingApp;
|
|
323
|
+
if (encoder) out.add('encoder', encoder, RANK_INFO);
|
|
324
|
+
if (muxingApp && writingApp && muxingApp !== writingApp) {
|
|
325
|
+
out.add(otherEmbeddedKey('webm', 'MuxingApp'), muxingApp, RANK_INFO, 'Muxing application');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Read a film's tags through random access.
|
|
330
|
+
*
|
|
331
|
+
* Null when the bytes do not open with an EBML header whose DocType is
|
|
332
|
+
* `webm` or `matroska`. Every candidate is `editable: false`.
|
|
333
|
+
*/ export async function readWebm(reader) {
|
|
334
|
+
const ebml = await headerAt(reader, 0);
|
|
335
|
+
if (!ebml || ebml.id !== ID_EBML || ebml.size === null) return null;
|
|
336
|
+
if (ebml.size > EBML_HEADER_MAX_BYTES) return null;
|
|
337
|
+
const header = await reader.read(ebml.body, ebml.body + ebml.size);
|
|
338
|
+
const docType = elements(header, 0, header.length).find((element)=>element.id === ID_DOCTYPE);
|
|
339
|
+
const kind = docType ? text(payload(header, docType)) : '';
|
|
340
|
+
if (kind !== 'webm' && kind !== 'matroska') return null;
|
|
341
|
+
// The Segment follows the header, perhaps after a Void.
|
|
342
|
+
let segment = null;
|
|
343
|
+
let at = ebml.body + ebml.size;
|
|
344
|
+
for(let count = 0; count < 16; count++){
|
|
345
|
+
const element = await headerAt(reader, at);
|
|
346
|
+
if (!element) break;
|
|
347
|
+
if (element.id === ID_SEGMENT) {
|
|
348
|
+
segment = element;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
if (element.size === null) break;
|
|
352
|
+
at = element.body + element.size;
|
|
353
|
+
}
|
|
354
|
+
const out = new Collector();
|
|
355
|
+
if (!segment) return {
|
|
356
|
+
candidates: []
|
|
357
|
+
};
|
|
358
|
+
const origin = segment.body;
|
|
359
|
+
const segmentEnd = segment.size === null ? reader.size : Math.min(reader.size, segment.body + segment.size);
|
|
360
|
+
const visited = new Set();
|
|
361
|
+
const infos = [];
|
|
362
|
+
const tags = [];
|
|
363
|
+
const pending = [];
|
|
364
|
+
const visit = async (element)=>{
|
|
365
|
+
if (visited.has(element.start) || element.size === null) return;
|
|
366
|
+
const cap = element.id === ID_SEEKHEAD ? SEEKHEAD_MAX_BYTES : element.id === ID_INFO ? INFO_MAX_BYTES : element.id === ID_TAGS ? TAGS_MAX_BYTES : 0;
|
|
367
|
+
if (!cap || element.size > cap) return;
|
|
368
|
+
visited.add(element.start);
|
|
369
|
+
const bytes = await reader.read(element.body, Math.min(segmentEnd, element.body + element.size));
|
|
370
|
+
if (element.id === ID_INFO) infos.push(bytes);
|
|
371
|
+
else if (element.id === ID_TAGS) tags.push(bytes);
|
|
372
|
+
else {
|
|
373
|
+
for (const seek of elements(bytes, 0, bytes.length)){
|
|
374
|
+
var _seek_size;
|
|
375
|
+
if (seek.id !== ID_SEEK) continue;
|
|
376
|
+
let id = null;
|
|
377
|
+
let position = null;
|
|
378
|
+
for (const field of elements(bytes, seek.body, seek.body + ((_seek_size = seek.size) != null ? _seek_size : 0))){
|
|
379
|
+
const value = payload(bytes, field);
|
|
380
|
+
if (field.id === ID_SEEKID) id = unsigned(value);
|
|
381
|
+
else if (field.id === ID_SEEKPOSITION) position = unsigned(value);
|
|
382
|
+
}
|
|
383
|
+
if (id !== null && position !== null && (id === ID_INFO || id === ID_TAGS || id === ID_SEEKHEAD)) {
|
|
384
|
+
pending.push({
|
|
385
|
+
id,
|
|
386
|
+
at: origin + position
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
// Walk the Segment's children up to the first Cluster.
|
|
393
|
+
at = origin;
|
|
394
|
+
for(let count = 0; count < MAX_TOP_LEVEL && at < segmentEnd; count++){
|
|
395
|
+
const element = await headerAt(reader, at);
|
|
396
|
+
if (!element || element.id === ID_CLUSTER || element.size === null) break;
|
|
397
|
+
await visit(element);
|
|
398
|
+
at = element.body + element.size;
|
|
399
|
+
}
|
|
400
|
+
// Then what the index says lives beyond it — a second SeekHead included,
|
|
401
|
+
// one level deep, which is as far as the specification nests them.
|
|
402
|
+
for(let round = 0; round < 2 && pending.length; round++){
|
|
403
|
+
const targets = pending.splice(0);
|
|
404
|
+
for (const target of targets){
|
|
405
|
+
if (visited.has(target.at) || target.at >= segmentEnd) continue;
|
|
406
|
+
const element = await headerAt(reader, target.at);
|
|
407
|
+
if (element && element.id === target.id) await visit(element);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
for (const bytes of tags)readTags(bytes, out);
|
|
411
|
+
for (const bytes of infos)readInfo(bytes, out);
|
|
412
|
+
return {
|
|
413
|
+
candidates: out.candidates()
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
//# sourceMappingURL=webm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/aglyn/src/lib/app-utils/media-embedded-metadata/webm.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The tags of a WebM or Matroska film (AGL-3331). Read-only, like MP4.\n *\n * Matroska is EBML (RFC 8794): every element is a variable-length ID, a\n * variable-length size and a payload. The file is an EBML header naming the\n * DocType, then one `Segment` whose top-level children include `Info`\n * (title, date, muxer) and `Tags`, and — the bulk of the file — `Cluster`s\n * of media (RFC 9559 §5.1).\n *\n * ## Reading without downloading the film\n *\n * Every read goes through the random-access reader and is sized exactly: an\n * element header is read in at most two pieces (see `headerAt`), so even the\n * header of a `Cluster` costs its own few bytes and nothing of its payload.\n * The Segment's children are walked from the start until the first\n * `Cluster`; anything that lives past the media — `Tags` usually does,\n * because a muxer writes them last — is reached through the `SeekHead`\n * index (RFC 9559 §5.1.1), whose positions count from the start of the\n * Segment's payload. A Segment of unknown size (a live recording, which is\n * what a browser's MediaRecorder writes) runs to the end of the file, and a\n * `Cluster` of unknown size simply ends the walk.\n *\n * ## What is read\n *\n * - `Info` (§5.1.2): `Title`, `DateUTC` (signed nanoseconds since\n * 2001-01-01T00:00:00Z), `WritingApp` — or `MuxingApp` when that is all\n * there is — as the encoder.\n * - `Tags` (§5.1.8): the `SimpleTag`s of every `Tag` that targets the whole\n * file — no track, edition, chapter or attachment UID — preferring target\n * level 50, the film itself. A per-track tag (ffmpeg writes a `DURATION`\n * for every track) describes a stream, not the file, and is skipped.\n * Nested SimpleTags are not read.\n *\n * Tags outrank `Info` for the same idea: `DATE_RECORDED` says when the\n * recording began, `DateUTC` only when the file was muxed.\n */\n\nimport {\n otherEmbeddedKey,\n type MediaEmbeddedCanonicalKey,\n} from '../media-embedded-fields'\nimport type { EmbeddedByteReader, EmbeddedCandidate } from './types'\n\n// Element IDs, with their length-marker bits, as the specifications list them.\nconst ID_EBML = 0x1a45dfa3\nconst ID_DOCTYPE = 0x4282\nconst ID_SEGMENT = 0x18538067\nconst ID_SEEKHEAD = 0x114d9b74\nconst ID_SEEK = 0x4dbb\nconst ID_SEEKID = 0x53ab\nconst ID_SEEKPOSITION = 0x53ac\nconst ID_INFO = 0x1549a966\nconst ID_TITLE = 0x7ba9\nconst ID_DATEUTC = 0x4461\nconst ID_MUXINGAPP = 0x4d80\nconst ID_WRITINGAPP = 0x5741\nconst ID_TAGS = 0x1254c367\nconst ID_TAG = 0x7373\nconst ID_TARGETS = 0x63c0\nconst ID_TARGETTYPEVALUE = 0x68ca\nconst ID_TAG_UIDS = new Set([0x63c5, 0x63c9, 0x63c4, 0x63c6])\nconst ID_SIMPLETAG = 0x67c8\nconst ID_TAGNAME = 0x45a3\nconst ID_TAGSTRING = 0x4487\nconst ID_CLUSTER = 0x1f43b675\n\nconst EBML_HEADER_MAX_BYTES = 4096\nconst SEEKHEAD_MAX_BYTES = 1024 * 1024\nconst INFO_MAX_BYTES = 1024 * 1024\nconst TAGS_MAX_BYTES = 8 * 1024 * 1024\n/** Top-level elements ahead of the first Cluster: a handful, in practice. */\nconst MAX_TOP_LEVEL = 256\nconst MAX_CHILDREN = 65536\n/** Milliseconds from 1970 to 2001-01-01T00:00:00Z, Matroska's epoch. */\nconst EPOCH_2001_MS = 978307200000\n\ninterface ElementHeader {\n id: number\n start: number\n /** Where the payload begins. */\n body: number\n /** The payload size; null for the \"unknown\" size (all value bits set). */\n size: number | null\n}\n\n/** The length of a VINT from its first byte's leading zeros (RFC 8794 §4). */\nfunction vintLength(first: number): number {\n for (let length = 1, mask = 0x80; length <= 8; length++, mask >>= 1) {\n if (first & mask) return length\n }\n return 0\n}\n\n/** An element ID: 1 to 4 bytes, marker bit kept (RFC 8794 §5). */\nfunction readId(\n bytes: Uint8Array,\n at: number,\n): { id: number; length: number } | null {\n const length = vintLength(bytes[at] ?? 0)\n if (!length || length > 4 || at + length > bytes.length) return null\n let id = 0\n for (let index = 0; index < length; index++)\n id = id * 256 + (bytes[at + index] ?? 0)\n return { id, length }\n}\n\n/**\n * An element data size: 1 to 8 bytes, marker bit dropped; every value bit\n * set means \"unknown\" (RFC 8794 §6.2). A size past 2^53 is refused.\n */\nfunction readSize(\n bytes: Uint8Array,\n at: number,\n): { size: number | null; length: number } | null {\n const first = bytes[at] ?? 0\n const length = vintLength(first)\n if (!length || at + length > bytes.length) return null\n let value = first & (0xff >> length)\n let allOnes = value === 0xff >> length\n for (let index = 1; index < length; index++) {\n const byte = bytes[at + index] ?? 0\n value = value * 256 + byte\n if (byte !== 0xff) allOnes = false\n }\n if (allOnes) return { size: null, length }\n return Number.isSafeInteger(value) ? { size: value, length } : null\n}\n\n/** The elements inside `bytes[start, end)`, clamped to it. */\nfunction elements(\n bytes: Uint8Array,\n start: number,\n end: number,\n): ElementHeader[] {\n const out: ElementHeader[] = []\n let at = start\n while (at < end && out.length < MAX_CHILDREN) {\n const id = readId(bytes, at)\n if (!id) break\n const size = readSize(bytes, at + id.length)\n if (!size) break\n const body = at + id.length + size.length\n if (body > end) break\n // An unknown size inside a buffer can only run to the buffer's end.\n const length =\n size.size === null ? end - body : Math.min(size.size, end - body)\n out.push({ id: id.id, start: at, body, size: length })\n at = body + length\n }\n return out\n}\n\nconst payload = (bytes: Uint8Array, element: ElementHeader) =>\n bytes.subarray(element.body, element.body + (element.size ?? 0))\n\n/** A Matroska string: UTF-8, possibly zero-padded (RFC 8794 §7.4, §7.5). */\nconst text = (bytes: Uint8Array) =>\n new TextDecoder('utf-8').decode(bytes).replace(/\\0+$/, '')\n\nfunction unsigned(bytes: Uint8Array): number | null {\n if (bytes.length > 8) return null\n let value = 0\n for (const byte of bytes) value = value * 256 + byte\n return Number.isSafeInteger(value) ? value : null\n}\n\n/**\n * One element header read through the reader in at most two sized pieces:\n * five bytes — a 4-byte ID and the first byte of the size, which says how\n * long the size is — then whatever of the size is still missing. For a\n * `Cluster` (a 4-byte ID) the first read is all header; for a shorter ID it\n * may run into that element's own payload, never into a Cluster's — so no\n * read reaches into the media this module means to skip.\n */\nasync function headerAt(\n reader: EmbeddedByteReader,\n at: number,\n): Promise<ElementHeader | null> {\n if (at < 0 || at >= reader.size) return null\n const head = await reader.read(at, Math.min(reader.size, at + 5))\n const id = readId(head, 0)\n if (!id) return null\n const sizeAt = at + id.length\n const held = head.subarray(id.length)\n const length = vintLength(held[0] ?? 0)\n if (!held.length || !length) return null\n let sizeBytes = held.subarray(0, length)\n if (held.length < length) {\n const rest = await reader.read(sizeAt + held.length, sizeAt + length)\n if (held.length + rest.length < length) return null\n sizeBytes = new Uint8Array(length)\n sizeBytes.set(held)\n sizeBytes.set(rest, held.length)\n }\n const size = readSize(sizeBytes, 0)\n if (!size) return null\n return { id: id.id, start: at, body: sizeAt + length, size: size.size }\n}\n\ntype VideoKey = Extract<\n MediaEmbeddedCanonicalKey,\n | 'title'\n | 'creator'\n | 'description'\n | 'comment'\n | 'createdAt'\n | 'copyright'\n | 'keywords'\n | 'encoder'\n>\n\n/** Official tag names (Matroska Media Container Tag Specifications). */\nconst TAG_KEYS: Record<string, VideoKey> = {\n TITLE: 'title',\n ARTIST: 'creator',\n DESCRIPTION: 'description',\n COMMENT: 'comment',\n DATE_RECORDED: 'createdAt',\n COPYRIGHT: 'copyright',\n KEYWORDS: 'keywords',\n ENCODER: 'encoder',\n}\n\n/**\n * A tag date — `YYYY-MM-DD hh:mm:ss.mss`, any tail of it omitted — in the\n * catalog's form. The tag specification names no zone, so none is added.\n */\nfunction normalizeTagDate(raw: string): string {\n const value = raw.trim()\n const match =\n /^(\\d{4}-\\d{2}-\\d{2})[T ](\\d{2}:\\d{2}(?::\\d{2})?)(?:[.,](\\d+))?(Z|[+-]\\d{2}:\\d{2})?$/.exec(\n value,\n )\n if (!match) return value\n const [, date, time, fraction, zone] = match\n return `${date}T${time}${fraction ? `.${fraction.slice(0, 3)}` : ''}${zone ?? ''}`\n}\n\nclass Collector {\n private readonly byKey = new Map<\n string,\n { candidate: EmbeddedCandidate; rank: number }\n >()\n\n add(key: string, value: string, rank: number, label?: string): void {\n const trimmed = value.trim()\n if (!trimmed) return\n let candidate: EmbeddedCandidate\n if (key === 'keywords') {\n const items = trimmed\n .split(/[,;]/)\n .map((item) => item.trim())\n .filter(Boolean)\n if (!items.length) return\n candidate = { key, value: items, source: 'webm', editable: false }\n } else if (key === 'creator') {\n candidate = { key, value: [trimmed], source: 'webm', editable: false }\n } else if (key === 'createdAt') {\n candidate = {\n key,\n value: normalizeTagDate(trimmed),\n source: 'webm',\n editable: false,\n }\n } else {\n candidate = { key, value: trimmed, source: 'webm', editable: false }\n if (label) candidate.label = label\n }\n const held = this.byKey.get(key)\n if (!held || rank < held.rank) this.byKey.set(key, { candidate, rank })\n }\n\n candidates(): EmbeddedCandidate[] {\n return [...this.byKey.values()].map(({ candidate }) => candidate)\n }\n}\n\n/** Ranks: a film-level tag, another whole-file tag, then Info. */\nconst RANK_TAG_FILM = 0\nconst RANK_TAG_OTHER = 10\nconst RANK_INFO = 20\n\nfunction readTags(bytes: Uint8Array, out: Collector): void {\n for (const tag of elements(bytes, 0, bytes.length)) {\n if (tag.id !== ID_TAG) continue\n const parts = elements(bytes, tag.body, tag.body + (tag.size ?? 0))\n const targets = parts.find((part) => part.id === ID_TARGETS)\n let level = 50\n let wholeFile = true\n if (targets) {\n for (const target of elements(\n bytes,\n targets.body,\n targets.body + (targets.size ?? 0),\n )) {\n const value = unsigned(payload(bytes, target))\n if (target.id === ID_TARGETTYPEVALUE && value !== null) level = value\n if (ID_TAG_UIDS.has(target.id) && value !== 0) wholeFile = false\n }\n }\n if (!wholeFile) continue\n const rank = level === 50 ? RANK_TAG_FILM : RANK_TAG_OTHER\n for (const simple of parts) {\n if (simple.id !== ID_SIMPLETAG) continue\n let name = ''\n let value: string | null = null\n for (const field of elements(\n bytes,\n simple.body,\n simple.body + (simple.size ?? 0),\n )) {\n if (field.id === ID_TAGNAME) name = text(payload(bytes, field)).trim()\n else if (field.id === ID_TAGSTRING) value = text(payload(bytes, field))\n }\n if (!name || value === null) continue\n const key = TAG_KEYS[name.toUpperCase()]\n if (key) out.add(key, value, rank)\n else out.add(otherEmbeddedKey('webm', name), value, rank, name)\n }\n }\n}\n\nfunction readInfo(bytes: Uint8Array, out: Collector): void {\n let muxingApp = ''\n let writingApp = ''\n for (const field of elements(bytes, 0, bytes.length)) {\n const value = payload(bytes, field)\n if (field.id === ID_TITLE) out.add('title', text(value), RANK_INFO)\n else if (field.id === ID_MUXINGAPP) muxingApp = text(value).trim()\n else if (field.id === ID_WRITINGAPP) writingApp = text(value).trim()\n else if (field.id === ID_DATEUTC && value.length === 8) {\n const nanoseconds = new DataView(\n value.buffer,\n value.byteOffset,\n value.byteLength,\n ).getBigInt64(0)\n const date = new Date(EPOCH_2001_MS + Number(nanoseconds / 1_000_000n))\n if (!Number.isNaN(date.getTime())) {\n out.add(\n 'createdAt',\n date.toISOString().replace(/\\.\\d{3}Z$/, 'Z'),\n RANK_INFO,\n )\n }\n }\n }\n const encoder = writingApp || muxingApp\n if (encoder) out.add('encoder', encoder, RANK_INFO)\n if (muxingApp && writingApp && muxingApp !== writingApp) {\n out.add(\n otherEmbeddedKey('webm', 'MuxingApp'),\n muxingApp,\n RANK_INFO,\n 'Muxing application',\n )\n }\n}\n\n/**\n * Read a film's tags through random access.\n *\n * Null when the bytes do not open with an EBML header whose DocType is\n * `webm` or `matroska`. Every candidate is `editable: false`.\n */\nexport async function readWebm(\n reader: EmbeddedByteReader,\n): Promise<{ candidates: EmbeddedCandidate[] } | null> {\n const ebml = await headerAt(reader, 0)\n if (!ebml || ebml.id !== ID_EBML || ebml.size === null) return null\n if (ebml.size > EBML_HEADER_MAX_BYTES) return null\n const header = await reader.read(ebml.body, ebml.body + ebml.size)\n const docType = elements(header, 0, header.length).find(\n (element) => element.id === ID_DOCTYPE,\n )\n const kind = docType ? text(payload(header, docType)) : ''\n if (kind !== 'webm' && kind !== 'matroska') return null\n\n // The Segment follows the header, perhaps after a Void.\n let segment: ElementHeader | null = null\n let at = ebml.body + ebml.size\n for (let count = 0; count < 16; count++) {\n const element = await headerAt(reader, at)\n if (!element) break\n if (element.id === ID_SEGMENT) {\n segment = element\n break\n }\n if (element.size === null) break\n at = element.body + element.size\n }\n const out = new Collector()\n if (!segment) return { candidates: [] }\n const origin = segment.body\n const segmentEnd =\n segment.size === null\n ? reader.size\n : Math.min(reader.size, segment.body + segment.size)\n\n const visited = new Set<number>()\n const infos: Uint8Array[] = []\n const tags: Uint8Array[] = []\n const pending: Array<{ id: number; at: number }> = []\n\n const visit = async (element: ElementHeader) => {\n if (visited.has(element.start) || element.size === null) return\n const cap =\n element.id === ID_SEEKHEAD\n ? SEEKHEAD_MAX_BYTES\n : element.id === ID_INFO\n ? INFO_MAX_BYTES\n : element.id === ID_TAGS\n ? TAGS_MAX_BYTES\n : 0\n if (!cap || element.size > cap) return\n visited.add(element.start)\n const bytes = await reader.read(\n element.body,\n Math.min(segmentEnd, element.body + element.size),\n )\n if (element.id === ID_INFO) infos.push(bytes)\n else if (element.id === ID_TAGS) tags.push(bytes)\n else {\n for (const seek of elements(bytes, 0, bytes.length)) {\n if (seek.id !== ID_SEEK) continue\n let id: number | null = null\n let position: number | null = null\n for (const field of elements(\n bytes,\n seek.body,\n seek.body + (seek.size ?? 0),\n )) {\n const value = payload(bytes, field)\n if (field.id === ID_SEEKID) id = unsigned(value)\n else if (field.id === ID_SEEKPOSITION) position = unsigned(value)\n }\n if (\n id !== null &&\n position !== null &&\n (id === ID_INFO || id === ID_TAGS || id === ID_SEEKHEAD)\n ) {\n pending.push({ id, at: origin + position })\n }\n }\n }\n }\n\n // Walk the Segment's children up to the first Cluster.\n at = origin\n for (let count = 0; count < MAX_TOP_LEVEL && at < segmentEnd; count++) {\n const element = await headerAt(reader, at)\n if (!element || element.id === ID_CLUSTER || element.size === null) break\n await visit(element)\n at = element.body + element.size\n }\n // Then what the index says lives beyond it — a second SeekHead included,\n // one level deep, which is as far as the specification nests them.\n for (let round = 0; round < 2 && pending.length; round++) {\n const targets = pending.splice(0)\n for (const target of targets) {\n if (visited.has(target.at) || target.at >= segmentEnd) continue\n const element = await headerAt(reader, target.at)\n if (element && element.id === target.id) await visit(element)\n }\n }\n\n for (const bytes of tags) readTags(bytes, out)\n for (const bytes of infos) readInfo(bytes, out)\n return { candidates: out.candidates() }\n}\n"],"names":["otherEmbeddedKey","ID_EBML","ID_DOCTYPE","ID_SEGMENT","ID_SEEKHEAD","ID_SEEK","ID_SEEKID","ID_SEEKPOSITION","ID_INFO","ID_TITLE","ID_DATEUTC","ID_MUXINGAPP","ID_WRITINGAPP","ID_TAGS","ID_TAG","ID_TARGETS","ID_TARGETTYPEVALUE","ID_TAG_UIDS","Set","ID_SIMPLETAG","ID_TAGNAME","ID_TAGSTRING","ID_CLUSTER","EBML_HEADER_MAX_BYTES","SEEKHEAD_MAX_BYTES","INFO_MAX_BYTES","TAGS_MAX_BYTES","MAX_TOP_LEVEL","MAX_CHILDREN","EPOCH_2001_MS","vintLength","first","length","mask","readId","bytes","at","id","index","readSize","value","allOnes","byte","size","Number","isSafeInteger","elements","start","end","out","body","Math","min","push","payload","element","subarray","text","TextDecoder","decode","replace","unsigned","headerAt","reader","held","head","read","sizeAt","sizeBytes","rest","Uint8Array","set","TAG_KEYS","TITLE","ARTIST","DESCRIPTION","COMMENT","DATE_RECORDED","COPYRIGHT","KEYWORDS","ENCODER","normalizeTagDate","raw","trim","match","exec","date","time","fraction","zone","slice","Collector","add","key","rank","label","trimmed","candidate","items","split","map","item","filter","Boolean","source","editable","byKey","get","candidates","values","Map","RANK_TAG_FILM","RANK_TAG_OTHER","RANK_INFO","readTags","tag","parts","targets","find","part","level","wholeFile","target","has","simple","name","field","toUpperCase","readInfo","muxingApp","writingApp","nanoseconds","DataView","buffer","byteOffset","byteLength","getBigInt64","Date","isNaN","getTime","toISOString","encoder","readWebm","ebml","header","docType","kind","segment","count","origin","segmentEnd","visited","infos","tags","pending","visit","cap","seek","position","round","splice"],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type ImageBlockUpdate, type ImageBlocks } from './image-blocks';
|
|
18
|
+
/**
|
|
19
|
+
* Reads the metadata blocks of a WebP. Returns `null` when the bytes are not
|
|
20
|
+
* a WebP; a damaged file yields what came before the damage.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readWebpBlocks(bytes: Uint8Array): ImageBlocks | null;
|
|
23
|
+
/**
|
|
24
|
+
* Writes an update into a WebP and returns the new file.
|
|
25
|
+
*
|
|
26
|
+
* An existing `EXIF` or `XMP ` chunk is replaced in place (later duplicates
|
|
27
|
+
* removed); a new one goes after the image — EXIF before any existing XMP,
|
|
28
|
+
* XMP after EXIF — as the extended format orders them. `null` removes every
|
|
29
|
+
* copy. The `VP8X` EXIF and XMP flags are set to match what the file then
|
|
30
|
+
* holds; ICC, alpha and animation flags are left as they were.
|
|
31
|
+
*
|
|
32
|
+
* Throws `EmbeddedWriteError` for a file that is not a WebP or is truncated,
|
|
33
|
+
* for a layout that is neither simple nor extended, for a simple file whose
|
|
34
|
+
* bitstream header cannot give the canvas size a conversion needs, and for
|
|
35
|
+
* an IRB or PNG text update.
|
|
36
|
+
*/
|
|
37
|
+
export declare function writeWebpBlocks(bytes: Uint8Array, update: ImageBlockUpdate): Uint8Array;
|