@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.
Files changed (80) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
  3. package/src/lib/app-utils/docs-help.generated.js +1 -0
  4. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  5. package/src/lib/app-utils/docs-index.generated.js +3 -3
  6. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  7. package/src/lib/app-utils/media-embedded-fields.d.ts +471 -0
  8. package/src/lib/app-utils/media-embedded-fields.js +619 -0
  9. package/src/lib/app-utils/media-embedded-fields.js.map +1 -0
  10. package/src/lib/app-utils/media-embedded-metadata/exif.d.ts +160 -0
  11. package/src/lib/app-utils/media-embedded-metadata/exif.js +1267 -0
  12. package/src/lib/app-utils/media-embedded-metadata/exif.js.map +1 -0
  13. package/src/lib/app-utils/media-embedded-metadata/gif.d.ts +44 -0
  14. package/src/lib/app-utils/media-embedded-metadata/gif.js +134 -0
  15. package/src/lib/app-utils/media-embedded-metadata/gif.js.map +1 -0
  16. package/src/lib/app-utils/media-embedded-metadata/heif.d.ts +23 -0
  17. package/src/lib/app-utils/media-embedded-metadata/heif.js +354 -0
  18. package/src/lib/app-utils/media-embedded-metadata/heif.js.map +1 -0
  19. package/src/lib/app-utils/media-embedded-metadata/icc.d.ts +22 -0
  20. package/src/lib/app-utils/media-embedded-metadata/icc.js +85 -0
  21. package/src/lib/app-utils/media-embedded-metadata/icc.js.map +1 -0
  22. package/src/lib/app-utils/media-embedded-metadata/image-blocks.d.ts +145 -0
  23. package/src/lib/app-utils/media-embedded-metadata/image-blocks.js +200 -0
  24. package/src/lib/app-utils/media-embedded-metadata/image-blocks.js.map +1 -0
  25. package/src/lib/app-utils/media-embedded-metadata/index.d.ts +93 -0
  26. package/src/lib/app-utils/media-embedded-metadata/index.js +744 -0
  27. package/src/lib/app-utils/media-embedded-metadata/index.js.map +1 -0
  28. package/src/lib/app-utils/media-embedded-metadata/iptc.d.ts +59 -0
  29. package/src/lib/app-utils/media-embedded-metadata/iptc.js +529 -0
  30. package/src/lib/app-utils/media-embedded-metadata/iptc.js.map +1 -0
  31. package/src/lib/app-utils/media-embedded-metadata/jpeg.d.ts +43 -0
  32. package/src/lib/app-utils/media-embedded-metadata/jpeg.js +456 -0
  33. package/src/lib/app-utils/media-embedded-metadata/jpeg.js.map +1 -0
  34. package/src/lib/app-utils/media-embedded-metadata/mp4.d.ts +37 -0
  35. package/src/lib/app-utils/media-embedded-metadata/mp4.js +621 -0
  36. package/src/lib/app-utils/media-embedded-metadata/mp4.js.map +1 -0
  37. package/src/lib/app-utils/media-embedded-metadata/ooxml.d.ts +131 -0
  38. package/src/lib/app-utils/media-embedded-metadata/ooxml.js +1058 -0
  39. package/src/lib/app-utils/media-embedded-metadata/ooxml.js.map +1 -0
  40. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.d.ts +223 -0
  41. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js +936 -0
  42. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js.map +1 -0
  43. package/src/lib/app-utils/media-embedded-metadata/pdf.d.ts +82 -0
  44. package/src/lib/app-utils/media-embedded-metadata/pdf.js +1167 -0
  45. package/src/lib/app-utils/media-embedded-metadata/pdf.js.map +1 -0
  46. package/src/lib/app-utils/media-embedded-metadata/png.d.ts +41 -0
  47. package/src/lib/app-utils/media-embedded-metadata/png.js +512 -0
  48. package/src/lib/app-utils/media-embedded-metadata/png.js.map +1 -0
  49. package/src/lib/app-utils/media-embedded-metadata/svg.d.ts +30 -0
  50. package/src/lib/app-utils/media-embedded-metadata/svg.js +154 -0
  51. package/src/lib/app-utils/media-embedded-metadata/svg.js.map +1 -0
  52. package/src/lib/app-utils/media-embedded-metadata/tiff.d.ts +40 -0
  53. package/src/lib/app-utils/media-embedded-metadata/tiff.js +158 -0
  54. package/src/lib/app-utils/media-embedded-metadata/tiff.js.map +1 -0
  55. package/src/lib/app-utils/media-embedded-metadata/types.d.ts +60 -0
  56. package/src/lib/app-utils/media-embedded-metadata/types.js +32 -0
  57. package/src/lib/app-utils/media-embedded-metadata/types.js.map +1 -0
  58. package/src/lib/app-utils/media-embedded-metadata/webm.d.ts +26 -0
  59. package/src/lib/app-utils/media-embedded-metadata/webm.js +417 -0
  60. package/src/lib/app-utils/media-embedded-metadata/webm.js.map +1 -0
  61. package/src/lib/app-utils/media-embedded-metadata/webp.d.ts +37 -0
  62. package/src/lib/app-utils/media-embedded-metadata/webp.js +272 -0
  63. package/src/lib/app-utils/media-embedded-metadata/webp.js.map +1 -0
  64. package/src/lib/app-utils/media-embedded-metadata/xml.d.ts +231 -0
  65. package/src/lib/app-utils/media-embedded-metadata/xml.js +598 -0
  66. package/src/lib/app-utils/media-embedded-metadata/xml.js.map +1 -0
  67. package/src/lib/app-utils/media-embedded-metadata/xmp.d.ts +100 -0
  68. package/src/lib/app-utils/media-embedded-metadata/xmp.js +1475 -0
  69. package/src/lib/app-utils/media-embedded-metadata/xmp.js.map +1 -0
  70. package/src/lib/app-utils/media-embedded-metadata/zip.d.ts +93 -0
  71. package/src/lib/app-utils/media-embedded-metadata/zip.js +531 -0
  72. package/src/lib/app-utils/media-embedded-metadata/zip.js.map +1 -0
  73. package/src/lib/app-utils/name-search.d.ts +12 -0
  74. package/src/lib/app-utils/name-search.js +11 -0
  75. package/src/lib/app-utils/name-search.js.map +1 -1
  76. package/src/lib/foundation/definitions/platform.types.d.ts +11 -0
  77. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  78. package/src/server.d.ts +1 -0
  79. package/src/server.js +7 -0
  80. package/src/server.js.map +1 -1
@@ -0,0 +1,529 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /**
18
+ * IPTC IIM, and the Photoshop image resource block that carries it
19
+ * (AGL-3331).
20
+ *
21
+ * ## IIM
22
+ *
23
+ * The IPTC Information Interchange Model (IIM 4.2) is a flat run of
24
+ * datasets: `0x1C`, record, dataset, a 2-byte big-endian length, the data.
25
+ * A length with its top bit set is the extended form (§1.5.4): the low 15
26
+ * bits count the length bytes that follow. Record 2 is the "application
27
+ * record" every photo tool writes; record 1:90 names the character set, and
28
+ * `ESC % G` (ISO 2022) there means UTF-8. Without it the text is whatever
29
+ * the writer used, so each value decodes as UTF-8 when it is valid UTF-8
30
+ * and as Latin-1 otherwise.
31
+ *
32
+ * ## Photoshop image resources (IRB)
33
+ *
34
+ * In a JPEG (APP13 after `Photoshop 3.0\0`), a TIFF (tag 34377) and a PSD,
35
+ * IIM travels inside an image resource block: a run of resources, each
36
+ * `8BIM`, a 2-byte id, a Pascal name padded to even, a 4-byte size and the
37
+ * data padded to even (Photoshop File Formats §"Image Resource Blocks").
38
+ * Resource 0x0404 holds the IIM and 0x0425 its MD5, which Photoshop and the
39
+ * Metadata Working Group use to notice IPTC edited behind their back, so
40
+ * the digest is kept in step with every write.
41
+ *
42
+ * ## Writing
43
+ *
44
+ * {@link writeIim} syncs every mapped key in the patch — adding, replacing
45
+ * or removing datasets, a list as one dataset per item — and leaves every
46
+ * other dataset byte-identical and where it was. {@link writeIrbIptc}
47
+ * rewrites 0x0404 (and 0x0425) and copies every other resource through
48
+ * byte for byte, in order.
49
+ */ import { createHash } from "crypto";
50
+ import { decodeLegacyText, isUtf8 } from "./exif.js";
51
+ /** The record 2 datasets this module maps, IIM 4.2 §6. */ const FIELDS = [
52
+ {
53
+ key: 'title',
54
+ dataset: 5
55
+ },
56
+ {
57
+ key: 'keywords',
58
+ dataset: 25,
59
+ repeatable: true
60
+ },
61
+ {
62
+ key: 'instructions',
63
+ dataset: 40
64
+ },
65
+ {
66
+ key: 'creator',
67
+ dataset: 80,
68
+ repeatable: true
69
+ },
70
+ {
71
+ key: 'city',
72
+ dataset: 90
73
+ },
74
+ {
75
+ key: 'state',
76
+ dataset: 95
77
+ },
78
+ {
79
+ key: 'country',
80
+ dataset: 101
81
+ },
82
+ {
83
+ key: 'headline',
84
+ dataset: 105
85
+ },
86
+ {
87
+ key: 'credit',
88
+ dataset: 110
89
+ },
90
+ {
91
+ key: 'source',
92
+ dataset: 115
93
+ },
94
+ {
95
+ key: 'copyright',
96
+ dataset: 116
97
+ },
98
+ {
99
+ key: 'description',
100
+ dataset: 120
101
+ }
102
+ ];
103
+ const DATE_CREATED = 55;
104
+ const TIME_CREATED = 60;
105
+ /**
106
+ * Record 2 datasets that are binary, not text, and so are never transcoded:
107
+ * RecordVersion and the ObjectData preview (IIM 4.2 §6.2, 2:200–2:202).
108
+ */ const BINARY_DATASETS = new Set([
109
+ 0,
110
+ 200,
111
+ 201,
112
+ 202
113
+ ]);
114
+ /** 1:90 = `ESC % G`: UTF-8, ISO 2022 / ISO-IR 196. */ const UTF8_CHARSET = new Uint8Array([
115
+ 0x1b,
116
+ 0x25,
117
+ 0x47
118
+ ]);
119
+ /** The 2-byte RecordVersion IIM 4 writes into 1:00 and 2:00. */ const RECORD_VERSION = new Uint8Array([
120
+ 0x00,
121
+ 0x04
122
+ ]);
123
+ const IRB_SIGNATURES = new Set([
124
+ '8BIM',
125
+ 'MeSa',
126
+ 'PHUT',
127
+ 'AgHg',
128
+ 'DCSR'
129
+ ]);
130
+ const IPTC_RESOURCE = 0x0404;
131
+ const IPTC_DIGEST_RESOURCE = 0x0425;
132
+ const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(Z|[+-]\d{2}:\d{2})?)?$/;
133
+ const utf8Lenient = new TextDecoder('utf-8');
134
+ const encoder = new TextEncoder();
135
+ function concat(parts) {
136
+ const out = new Uint8Array(parts.reduce((n, part)=>n + part.length, 0));
137
+ let at = 0;
138
+ for (const part of parts){
139
+ out.set(part, at);
140
+ at += part.length;
141
+ }
142
+ return out;
143
+ }
144
+ function sameBytes(a, b) {
145
+ if (a.length !== b.length) return false;
146
+ for(let i = 0; i < a.length; i++)if (a[i] !== b[i]) return false;
147
+ return true;
148
+ }
149
+ function isAscii(text) {
150
+ for(let i = 0; i < text.length; i++)if (text.charCodeAt(i) > 0x7f) return false;
151
+ return true;
152
+ }
153
+ /**
154
+ * Datasets up to the first byte that does not start one. Whatever follows
155
+ * (padding, a truncated dataset, junk) is `rest`.
156
+ */ function parseIim(iim) {
157
+ const datasets = [];
158
+ let at = 0;
159
+ while(at + 5 <= iim.length && iim[at] === 0x1c){
160
+ var _iim_, _iim_1, _iim_2, _iim_3;
161
+ let size = ((_iim_ = iim[at + 3]) != null ? _iim_ : 0) << 8 | ((_iim_1 = iim[at + 4]) != null ? _iim_1 : 0);
162
+ let header = 5;
163
+ if (size & 0x8000) {
164
+ var _iim_4;
165
+ // IIM 4.2 §1.5.4: the extended form counts its own length bytes.
166
+ const bytes = size & 0x7fff;
167
+ if (bytes < 1 || bytes > 4 || at + 5 + bytes > iim.length) break;
168
+ size = 0;
169
+ for(let i = 0; i < bytes; i++)size = size * 256 + ((_iim_4 = iim[at + 5 + i]) != null ? _iim_4 : 0);
170
+ header += bytes;
171
+ }
172
+ if (at + header + size > iim.length) break;
173
+ datasets.push({
174
+ record: (_iim_2 = iim[at + 1]) != null ? _iim_2 : 0,
175
+ dataset: (_iim_3 = iim[at + 2]) != null ? _iim_3 : 0,
176
+ data: iim.subarray(at + header, at + header + size),
177
+ raw: iim.subarray(at, at + header + size)
178
+ });
179
+ at += header + size;
180
+ }
181
+ return {
182
+ datasets,
183
+ rest: iim.subarray(at)
184
+ };
185
+ }
186
+ function encodeDataset(record, dataset, data) {
187
+ if (data.length <= 0x7fff) {
188
+ return concat([
189
+ [
190
+ 0x1c,
191
+ record,
192
+ dataset,
193
+ data.length >> 8,
194
+ data.length & 0xff
195
+ ],
196
+ data
197
+ ]);
198
+ }
199
+ const n = data.length;
200
+ return concat([
201
+ [
202
+ 0x1c,
203
+ record,
204
+ dataset,
205
+ 0x80,
206
+ 0x04
207
+ ],
208
+ [
209
+ n >>> 24 & 0xff,
210
+ n >>> 16 & 0xff,
211
+ n >>> 8 & 0xff,
212
+ n & 0xff
213
+ ],
214
+ data
215
+ ]);
216
+ }
217
+ /** Whether 1:90 declares UTF-8: `ESC % G`, or ISO 2022's `ESC % / G|H|I`. */ function declaresUtf8(datasets) {
218
+ var _d_;
219
+ const charset = datasets.find((d)=>d.record === 1 && d.dataset === 90);
220
+ const d = charset == null ? void 0 : charset.data;
221
+ if (!d || d[0] !== 0x1b || d[1] !== 0x25) return false;
222
+ return d[2] === 0x47 || d[2] === 0x2f && [
223
+ 0x47,
224
+ 0x48,
225
+ 0x49
226
+ ].includes((_d_ = d[3]) != null ? _d_ : 0);
227
+ }
228
+ function iimEntryText(data, utf8) {
229
+ const text = utf8 ? utf8Lenient.decode(data) : decodeLegacyText(data);
230
+ const nul = text.indexOf('\0');
231
+ return (nul >= 0 ? text.slice(0, nul) : text).trim();
232
+ }
233
+ /**
234
+ * 2:55 "CCYYMMDD" and 2:60 "HHMMSS±HHMM" as ISO 8601. The zone is kept as
235
+ * recorded; a time with none (some writers drop it) stays local.
236
+ */ function iptcDate(date, time) {
237
+ const d = date ? /^(\d{4})(\d{2})(\d{2})$/.exec(date) : null;
238
+ if (!d) return null;
239
+ const [, year, month, day] = d;
240
+ if (!year || !month || !day || year === '0000') return null;
241
+ if (+month < 1 || +month > 12 || +day < 1 || +day > 31) return null;
242
+ const iso = `${year}-${month}-${day}`;
243
+ const t = time ? /^(\d{2}):?(\d{2}):?(\d{2})(?:([+-])(\d{2}):?(\d{2}))?$/.exec(time) : null;
244
+ if (!t) return iso;
245
+ const [, hour, minute, second, sign, zoneHour, zoneMinute] = t;
246
+ if (+(hour != null ? hour : 99) > 23 || +(minute != null ? minute : 99) > 59 || +(second != null ? second : 99) > 60) {
247
+ return iso;
248
+ }
249
+ const zone = sign ? `${sign}${zoneHour}:${zoneMinute}` : '';
250
+ return `${iso}T${hour}:${minute}:${second}${zone}`;
251
+ }
252
+ /**
253
+ * An IIM stream without the zero padding its container added after the
254
+ * last dataset (a TIFF tag pads to four bytes). Zeros inside a dataset are
255
+ * data and are kept, as is anything after the datasets that is not zero.
256
+ */ export function trimIimPadding(iim) {
257
+ const { rest } = parseIim(iim);
258
+ return rest.length && rest.every((byte)=>byte === 0) ? iim.subarray(0, iim.length - rest.length) : iim;
259
+ }
260
+ /** Read the mapped record 2 datasets of an IIM stream. Never throws. */ export function readIim(iim) {
261
+ try {
262
+ var _texts_, _texts_1;
263
+ const { datasets } = parseIim(iim);
264
+ const utf8 = declaresUtf8(datasets);
265
+ const texts = (dataset)=>datasets.filter((d)=>d.record === 2 && d.dataset === dataset).map((d)=>iimEntryText(d.data, utf8)).filter(Boolean);
266
+ const out = [];
267
+ for (const field of FIELDS){
268
+ const [first, ...more] = texts(field.dataset);
269
+ if (first === undefined) continue;
270
+ out.push({
271
+ key: field.key,
272
+ value: field.repeatable ? [
273
+ ...new Set([
274
+ first,
275
+ ...more
276
+ ])
277
+ ] : first,
278
+ source: 'iptc'
279
+ });
280
+ }
281
+ const created = iptcDate((_texts_ = texts(DATE_CREATED)[0]) != null ? _texts_ : null, (_texts_1 = texts(TIME_CREATED)[0]) != null ? _texts_1 : null);
282
+ if (created) out.push({
283
+ key: 'createdAt',
284
+ value: created,
285
+ source: 'iptc'
286
+ });
287
+ return out;
288
+ } catch (unused) {
289
+ return [];
290
+ }
291
+ }
292
+ /** A patch value as the list of dataset values it becomes. */ function iimEntryValues(value, repeatable) {
293
+ if (value === null) return [];
294
+ const items = (Array.isArray(value) ? value : [
295
+ value
296
+ ]).map((item)=>item.trim()).filter(Boolean);
297
+ if (repeatable) return [
298
+ ...new Set(items)
299
+ ];
300
+ return items.length ? [
301
+ items.join(', ')
302
+ ] : [];
303
+ }
304
+ /** The index of the first dataset that sorts after record:dataset. */ function sortedIndex(list, record, dataset) {
305
+ const index = list.findIndex((d)=>d.record > record || d.record === record && d.dataset > dataset);
306
+ return index < 0 ? list.length : index;
307
+ }
308
+ /**
309
+ * Sync the mapped keys of `patch` into an IIM stream: every key present is
310
+ * made to hold exactly the patched value(s), `null` removing its datasets.
311
+ * Datasets this module does not map (2:00 RecordVersion included) keep
312
+ * their bytes and their order; new ones go where record:dataset order puts
313
+ * them. Returns `iim` itself when nothing changed.
314
+ *
315
+ * Text is written as UTF-8. When a value is not ASCII and 1:90 does not
316
+ * already say UTF-8, 1:90 is set to `ESC % G` and the other record 2 text
317
+ * is transcoded from Latin-1 so it does not turn to mojibake under the new
318
+ * declaration. IIM's per-dataset length limits (64 bytes for a keyword,
319
+ * 2000 for a caption) are not enforced: every current reader ignores them,
320
+ * and truncating someone's caption is worse than exceeding a 1999 limit.
321
+ */ export function writeIim(iim, patch) {
322
+ const { datasets, rest } = parseIim(iim);
323
+ const patched = (key)=>Object.prototype.hasOwnProperty.call(patch, key) && patch[key] !== undefined;
324
+ const edits = new Map();
325
+ for (const field of FIELDS){
326
+ const value = patch[field.key];
327
+ if (patched(field.key) && value !== undefined) {
328
+ edits.set(field.dataset, iimEntryValues(value, Boolean(field.repeatable)));
329
+ }
330
+ }
331
+ if (patched('createdAt')) {
332
+ const value = patch['createdAt'];
333
+ const iso = typeof value === 'string' ? ISO_DATE.exec(value.trim()) : null;
334
+ if (value === null) {
335
+ edits.set(DATE_CREATED, []);
336
+ edits.set(TIME_CREATED, []);
337
+ } else if (iso) {
338
+ var _ref;
339
+ const [, year, month, day, hour, minute, second, zone] = iso;
340
+ edits.set(DATE_CREATED, [
341
+ `${year}${month}${day}`
342
+ ]);
343
+ // No zone recorded, none invented: the time is written without one.
344
+ const offset = zone === 'Z' ? '+0000' : (_ref = zone == null ? void 0 : zone.replace(':', '')) != null ? _ref : '';
345
+ edits.set(TIME_CREATED, hour ? [
346
+ `${hour}${minute}${second != null ? second : '00'}${offset}`
347
+ ] : []);
348
+ }
349
+ }
350
+ if (!edits.size) return iim;
351
+ let list = datasets.slice();
352
+ const values = [
353
+ ...edits.values()
354
+ ].flat();
355
+ if (!declaresUtf8(list) && values.some((v)=>!isAscii(v))) {
356
+ list = list.map((d)=>d.record === 2 && !BINARY_DATASETS.has(d.dataset) && !edits.has(d.dataset) && !isUtf8(d.data) ? _extends({}, d, {
357
+ data: encoder.encode(decodeLegacyText(d.data)),
358
+ raw: null
359
+ }) : d);
360
+ const charset = {
361
+ record: 1,
362
+ dataset: 90,
363
+ data: UTF8_CHARSET,
364
+ raw: null
365
+ };
366
+ const existing = list.findIndex((d)=>d.record === 1 && d.dataset === 90);
367
+ if (existing >= 0) {
368
+ list[existing] = charset;
369
+ } else {
370
+ if (!list.some((d)=>d.record === 1)) {
371
+ // IIM 4.2 §5: 1:00 ModelVersion is mandatory in the envelope.
372
+ list.splice(sortedIndex(list, 1, 0), 0, {
373
+ record: 1,
374
+ dataset: 0,
375
+ data: RECORD_VERSION,
376
+ raw: null
377
+ });
378
+ }
379
+ list.splice(sortedIndex(list, 1, 90), 0, charset);
380
+ }
381
+ }
382
+ for (const [dataset, texts] of [
383
+ ...edits
384
+ ].sort((a, b)=>a[0] - b[0])){
385
+ const first = list.findIndex((d)=>d.record === 2 && d.dataset === dataset);
386
+ const kept = list.filter((d)=>!(d.record === 2 && d.dataset === dataset));
387
+ const fresh = texts.map((text)=>({
388
+ record: 2,
389
+ dataset,
390
+ data: encoder.encode(text),
391
+ raw: null
392
+ }));
393
+ kept.splice(first >= 0 ? first : sortedIndex(kept, 2, dataset), 0, ...fresh);
394
+ list = kept;
395
+ }
396
+ if (values.length && !list.some((d)=>d.record === 2 && d.dataset === 0)) {
397
+ // IIM 4.2 §6: 2:00 RecordVersion is mandatory in the application record.
398
+ list.splice(sortedIndex(list, 2, 0), 0, {
399
+ record: 2,
400
+ dataset: 0,
401
+ data: RECORD_VERSION,
402
+ raw: null
403
+ });
404
+ }
405
+ // Zero padding after the last dataset is dropped (a container pads again
406
+ // as it needs to); anything else there is kept as found.
407
+ const tail = rest.some((byte)=>byte !== 0) ? rest : new Uint8Array();
408
+ const out = concat([
409
+ ...list.map((d)=>{
410
+ var _d_raw;
411
+ return (_d_raw = d.raw) != null ? _d_raw : encodeDataset(d.record, d.dataset, d.data);
412
+ }),
413
+ tail
414
+ ]);
415
+ return sameBytes(out, iim) ? iim : out;
416
+ }
417
+ function parseIrb(irb) {
418
+ const resources = [];
419
+ let at = 0;
420
+ while(at + 12 <= irb.length){
421
+ var _irb_, _irb_nameEnd, _irb_1, _irb_2, _irb_3, _irb_4, _irb_5;
422
+ const signature = String.fromCharCode(...irb.subarray(at, at + 4));
423
+ if (!IRB_SIGNATURES.has(signature)) break;
424
+ const nameLength = (_irb_ = irb[at + 6]) != null ? _irb_ : 0;
425
+ const nameEnd = at + 6 + (nameLength + 2 & ~1);
426
+ if (nameEnd + 4 > irb.length) break;
427
+ const size = ((_irb_nameEnd = irb[nameEnd]) != null ? _irb_nameEnd : 0) * 0x1000000 + (((_irb_1 = irb[nameEnd + 1]) != null ? _irb_1 : 0) << 16 | ((_irb_2 = irb[nameEnd + 2]) != null ? _irb_2 : 0) << 8 | ((_irb_3 = irb[nameEnd + 3]) != null ? _irb_3 : 0));
428
+ const dataAt = nameEnd + 4;
429
+ if (dataAt + size > irb.length) break;
430
+ const end = Math.min(dataAt + size + size % 2, irb.length);
431
+ resources.push({
432
+ signature,
433
+ id: ((_irb_4 = irb[at + 4]) != null ? _irb_4 : 0) << 8 | ((_irb_5 = irb[at + 5]) != null ? _irb_5 : 0),
434
+ name: irb.subarray(at + 6, nameEnd),
435
+ data: irb.subarray(dataAt, dataAt + size),
436
+ raw: irb.subarray(at, end)
437
+ });
438
+ at = end;
439
+ }
440
+ return {
441
+ resources,
442
+ rest: irb.subarray(at)
443
+ };
444
+ }
445
+ function encodeResource(resource, data) {
446
+ const n = data.length;
447
+ return concat([
448
+ [
449
+ ...resource.signature
450
+ ].map((c)=>c.charCodeAt(0)),
451
+ [
452
+ resource.id >> 8,
453
+ resource.id & 0xff
454
+ ],
455
+ resource.name,
456
+ [
457
+ n >>> 24 & 0xff,
458
+ n >>> 16 & 0xff,
459
+ n >>> 8 & 0xff,
460
+ n & 0xff
461
+ ],
462
+ data,
463
+ n % 2 ? [
464
+ 0
465
+ ] : []
466
+ ]);
467
+ }
468
+ const isIptcResource = (r)=>r.signature === '8BIM' && r.id === IPTC_RESOURCE;
469
+ /** The IIM held in an image resource block (resource 0x0404), or null. */ export function irbIim(irb) {
470
+ try {
471
+ var _ref;
472
+ var _parseIrb_resources_find;
473
+ return (_ref = (_parseIrb_resources_find = parseIrb(irb).resources.find(isIptcResource)) == null ? void 0 : _parseIrb_resources_find.data) != null ? _ref : null;
474
+ } catch (unused) {
475
+ return null;
476
+ }
477
+ }
478
+ /** Whether an image resource block holds IPTC (resource 0x0404). */ export function irbHasIptc(irb) {
479
+ return irbIim(irb) !== null;
480
+ }
481
+ /** Read the IPTC of an image resource block. Never throws. */ export function readIrbIptc(irb) {
482
+ const iim = irbIim(irb);
483
+ return iim ? readIim(iim) : [];
484
+ }
485
+ /**
486
+ * Put `iim` in resource 0x0404, or take the resource out when `iim` is
487
+ * null, and bring the 0x0425 digest in step (set to the new IIM's MD5, or
488
+ * removed with it). A block without 0x0404 gets one at the end. Every other
489
+ * resource, and anything after the last one, is copied byte for byte.
490
+ */ export function replaceIrbIim(irb, iim) {
491
+ const { resources, rest } = parseIrb(irb);
492
+ const digest = iim ? new Uint8Array(createHash('md5').update(iim).digest()) : null;
493
+ const parts = [];
494
+ let replaced = false;
495
+ for (const resource of resources){
496
+ if (isIptcResource(resource) && !replaced) {
497
+ replaced = true;
498
+ if (iim) parts.push(encodeResource(resource, iim));
499
+ } else if (resource.signature === '8BIM' && resource.id === IPTC_DIGEST_RESOURCE) {
500
+ if (digest) parts.push(encodeResource(resource, digest));
501
+ } else {
502
+ parts.push(resource.raw);
503
+ }
504
+ }
505
+ if (!replaced && iim) {
506
+ parts.push(encodeResource({
507
+ signature: '8BIM',
508
+ id: IPTC_RESOURCE,
509
+ name: new Uint8Array(2)
510
+ }, iim));
511
+ }
512
+ const out = concat([
513
+ ...parts,
514
+ rest
515
+ ]);
516
+ return sameBytes(out, irb) ? irb : out;
517
+ }
518
+ /**
519
+ * Write a patch into the IPTC of an image resource block. A block without
520
+ * IPTC is returned as is: new values go to XMP, never into a new IIM (the
521
+ * Metadata Working Group's rule).
522
+ */ export function writeIrbIptc(irb, patch) {
523
+ const iim = irbIim(irb);
524
+ if (!iim) return irb;
525
+ const next = writeIim(iim, patch);
526
+ return next === iim ? irb : replaceIrbIim(irb, next);
527
+ }
528
+
529
+ //# sourceMappingURL=iptc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/aglyn/src/lib/app-utils/media-embedded-metadata/iptc.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 * IPTC IIM, and the Photoshop image resource block that carries it\n * (AGL-3331).\n *\n * ## IIM\n *\n * The IPTC Information Interchange Model (IIM 4.2) is a flat run of\n * datasets: `0x1C`, record, dataset, a 2-byte big-endian length, the data.\n * A length with its top bit set is the extended form (§1.5.4): the low 15\n * bits count the length bytes that follow. Record 2 is the \"application\n * record\" every photo tool writes; record 1:90 names the character set, and\n * `ESC % G` (ISO 2022) there means UTF-8. Without it the text is whatever\n * the writer used, so each value decodes as UTF-8 when it is valid UTF-8\n * and as Latin-1 otherwise.\n *\n * ## Photoshop image resources (IRB)\n *\n * In a JPEG (APP13 after `Photoshop 3.0\\0`), a TIFF (tag 34377) and a PSD,\n * IIM travels inside an image resource block: a run of resources, each\n * `8BIM`, a 2-byte id, a Pascal name padded to even, a 4-byte size and the\n * data padded to even (Photoshop File Formats §\"Image Resource Blocks\").\n * Resource 0x0404 holds the IIM and 0x0425 its MD5, which Photoshop and the\n * Metadata Working Group use to notice IPTC edited behind their back, so\n * the digest is kept in step with every write.\n *\n * ## Writing\n *\n * {@link writeIim} syncs every mapped key in the patch — adding, replacing\n * or removing datasets, a list as one dataset per item — and leaves every\n * other dataset byte-identical and where it was. {@link writeIrbIptc}\n * rewrites 0x0404 (and 0x0425) and copies every other resource through\n * byte for byte, in order.\n */\n\nimport { createHash } from 'crypto'\n\nimport { decodeLegacyText, isUtf8 } from './exif'\nimport type { EmbeddedCandidate, EmbeddedPatch } from './types'\n\n/** The record 2 datasets this module maps, IIM 4.2 §6. */\nconst FIELDS: ReadonlyArray<{\n key: string\n dataset: number\n repeatable?: boolean\n}> = [\n { key: 'title', dataset: 5 }, // Object Name\n { key: 'keywords', dataset: 25, repeatable: true },\n { key: 'instructions', dataset: 40 }, // Special Instructions\n { key: 'creator', dataset: 80, repeatable: true }, // By-line\n { key: 'city', dataset: 90 },\n { key: 'state', dataset: 95 }, // Province/State\n { key: 'country', dataset: 101 }, // Country/Primary Location Name\n { key: 'headline', dataset: 105 },\n { key: 'credit', dataset: 110 },\n { key: 'source', dataset: 115 },\n { key: 'copyright', dataset: 116 }, // Copyright Notice\n { key: 'description', dataset: 120 }, // Caption/Abstract\n]\n\nconst DATE_CREATED = 55\nconst TIME_CREATED = 60\n\n/**\n * Record 2 datasets that are binary, not text, and so are never transcoded:\n * RecordVersion and the ObjectData preview (IIM 4.2 §6.2, 2:200–2:202).\n */\nconst BINARY_DATASETS: ReadonlySet<number> = new Set([0, 200, 201, 202])\n\n/** 1:90 = `ESC % G`: UTF-8, ISO 2022 / ISO-IR 196. */\nconst UTF8_CHARSET = new Uint8Array([0x1b, 0x25, 0x47])\n\n/** The 2-byte RecordVersion IIM 4 writes into 1:00 and 2:00. */\nconst RECORD_VERSION = new Uint8Array([0x00, 0x04])\n\nconst IRB_SIGNATURES: ReadonlySet<string> = new Set([\n '8BIM',\n 'MeSa',\n 'PHUT',\n 'AgHg',\n 'DCSR',\n])\nconst IPTC_RESOURCE = 0x0404\nconst IPTC_DIGEST_RESOURCE = 0x0425\n\nconst ISO_DATE =\n /^(\\d{4})-(\\d{2})-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2})?)?$/\n\nconst utf8Lenient = new TextDecoder('utf-8')\nconst encoder = new TextEncoder()\n\ninterface IimEntry {\n record: number\n dataset: number\n data: Uint8Array\n /** The dataset exactly as stored, header included; null when new. */\n raw: Uint8Array | null\n}\n\nfunction concat(parts: ArrayLike<number>[]): Uint8Array {\n const out = new Uint8Array(parts.reduce((n, part) => n + part.length, 0))\n let at = 0\n for (const part of parts) {\n out.set(part, at)\n at += part.length\n }\n return out\n}\n\nfunction sameBytes(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false\n return true\n}\n\nfunction isAscii(text: string): boolean {\n for (let i = 0; i < text.length; i++)\n if (text.charCodeAt(i) > 0x7f) return false\n return true\n}\n\n/**\n * Datasets up to the first byte that does not start one. Whatever follows\n * (padding, a truncated dataset, junk) is `rest`.\n */\nfunction parseIim(iim: Uint8Array): { datasets: IimEntry[]; rest: Uint8Array } {\n const datasets: IimEntry[] = []\n let at = 0\n while (at + 5 <= iim.length && iim[at] === 0x1c) {\n let size = ((iim[at + 3] ?? 0) << 8) | (iim[at + 4] ?? 0)\n let header = 5\n if (size & 0x8000) {\n // IIM 4.2 §1.5.4: the extended form counts its own length bytes.\n const bytes = size & 0x7fff\n if (bytes < 1 || bytes > 4 || at + 5 + bytes > iim.length) break\n size = 0\n for (let i = 0; i < bytes; i++) size = size * 256 + (iim[at + 5 + i] ?? 0)\n header += bytes\n }\n if (at + header + size > iim.length) break\n datasets.push({\n record: iim[at + 1] ?? 0,\n dataset: iim[at + 2] ?? 0,\n data: iim.subarray(at + header, at + header + size),\n raw: iim.subarray(at, at + header + size),\n })\n at += header + size\n }\n return { datasets, rest: iim.subarray(at) }\n}\n\nfunction encodeDataset(record: number, dataset: number, data: Uint8Array) {\n if (data.length <= 0x7fff) {\n return concat([\n [0x1c, record, dataset, data.length >> 8, data.length & 0xff],\n data,\n ])\n }\n const n = data.length\n return concat([\n [0x1c, record, dataset, 0x80, 0x04],\n [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff],\n data,\n ])\n}\n\n/** Whether 1:90 declares UTF-8: `ESC % G`, or ISO 2022's `ESC % / G|H|I`. */\nfunction declaresUtf8(datasets: readonly IimEntry[]): boolean {\n const charset = datasets.find((d) => d.record === 1 && d.dataset === 90)\n const d = charset?.data\n if (!d || d[0] !== 0x1b || d[1] !== 0x25) return false\n return (\n d[2] === 0x47 || (d[2] === 0x2f && [0x47, 0x48, 0x49].includes(d[3] ?? 0))\n )\n}\n\nfunction iimEntryText(data: Uint8Array, utf8: boolean): string {\n const text = utf8 ? utf8Lenient.decode(data) : decodeLegacyText(data)\n const nul = text.indexOf('\\0')\n return (nul >= 0 ? text.slice(0, nul) : text).trim()\n}\n\n/**\n * 2:55 \"CCYYMMDD\" and 2:60 \"HHMMSS±HHMM\" as ISO 8601. The zone is kept as\n * recorded; a time with none (some writers drop it) stays local.\n */\nfunction iptcDate(date: string | null, time: string | null): string | null {\n const d = date ? /^(\\d{4})(\\d{2})(\\d{2})$/.exec(date) : null\n if (!d) return null\n const [, year, month, day] = d\n if (!year || !month || !day || year === '0000') return null\n if (+month < 1 || +month > 12 || +day < 1 || +day > 31) return null\n const iso = `${year}-${month}-${day}`\n const t = time\n ? /^(\\d{2}):?(\\d{2}):?(\\d{2})(?:([+-])(\\d{2}):?(\\d{2}))?$/.exec(time)\n : null\n if (!t) return iso\n const [, hour, minute, second, sign, zoneHour, zoneMinute] = t\n if (+(hour ?? 99) > 23 || +(minute ?? 99) > 59 || +(second ?? 99) > 60) {\n return iso\n }\n const zone = sign ? `${sign}${zoneHour}:${zoneMinute}` : ''\n return `${iso}T${hour}:${minute}:${second}${zone}`\n}\n\n/**\n * An IIM stream without the zero padding its container added after the\n * last dataset (a TIFF tag pads to four bytes). Zeros inside a dataset are\n * data and are kept, as is anything after the datasets that is not zero.\n */\nexport function trimIimPadding(iim: Uint8Array): Uint8Array {\n const { rest } = parseIim(iim)\n return rest.length && rest.every((byte) => byte === 0)\n ? iim.subarray(0, iim.length - rest.length)\n : iim\n}\n\n/** Read the mapped record 2 datasets of an IIM stream. Never throws. */\nexport function readIim(iim: Uint8Array): EmbeddedCandidate[] {\n try {\n const { datasets } = parseIim(iim)\n const utf8 = declaresUtf8(datasets)\n const texts = (dataset: number) =>\n datasets\n .filter((d) => d.record === 2 && d.dataset === dataset)\n .map((d) => iimEntryText(d.data, utf8))\n .filter(Boolean)\n const out: EmbeddedCandidate[] = []\n for (const field of FIELDS) {\n const [first, ...more] = texts(field.dataset)\n if (first === undefined) continue\n out.push({\n key: field.key,\n value: field.repeatable ? [...new Set([first, ...more])] : first,\n source: 'iptc',\n })\n }\n const created = iptcDate(\n texts(DATE_CREATED)[0] ?? null,\n texts(TIME_CREATED)[0] ?? null,\n )\n if (created) out.push({ key: 'createdAt', value: created, source: 'iptc' })\n return out\n } catch {\n return []\n }\n}\n\n/** A patch value as the list of dataset values it becomes. */\nfunction iimEntryValues(\n value: string | string[] | null,\n repeatable: boolean,\n): string[] {\n if (value === null) return []\n const items = (Array.isArray(value) ? value : [value])\n .map((item) => item.trim())\n .filter(Boolean)\n if (repeatable) return [...new Set(items)]\n return items.length ? [items.join(', ')] : []\n}\n\n/** The index of the first dataset that sorts after record:dataset. */\nfunction sortedIndex(\n list: readonly IimEntry[],\n record: number,\n dataset: number,\n) {\n const index = list.findIndex(\n (d) => d.record > record || (d.record === record && d.dataset > dataset),\n )\n return index < 0 ? list.length : index\n}\n\n/**\n * Sync the mapped keys of `patch` into an IIM stream: every key present is\n * made to hold exactly the patched value(s), `null` removing its datasets.\n * Datasets this module does not map (2:00 RecordVersion included) keep\n * their bytes and their order; new ones go where record:dataset order puts\n * them. Returns `iim` itself when nothing changed.\n *\n * Text is written as UTF-8. When a value is not ASCII and 1:90 does not\n * already say UTF-8, 1:90 is set to `ESC % G` and the other record 2 text\n * is transcoded from Latin-1 so it does not turn to mojibake under the new\n * declaration. IIM's per-dataset length limits (64 bytes for a keyword,\n * 2000 for a caption) are not enforced: every current reader ignores them,\n * and truncating someone's caption is worse than exceeding a 1999 limit.\n */\nexport function writeIim(iim: Uint8Array, patch: EmbeddedPatch): Uint8Array {\n const { datasets, rest } = parseIim(iim)\n const patched = (key: string) =>\n Object.prototype.hasOwnProperty.call(patch, key) && patch[key] !== undefined\n const edits = new Map<number, string[]>()\n for (const field of FIELDS) {\n const value = patch[field.key]\n if (patched(field.key) && value !== undefined) {\n edits.set(field.dataset, iimEntryValues(value, Boolean(field.repeatable)))\n }\n }\n if (patched('createdAt')) {\n const value = patch['createdAt']\n const iso = typeof value === 'string' ? ISO_DATE.exec(value.trim()) : null\n if (value === null) {\n edits.set(DATE_CREATED, [])\n edits.set(TIME_CREATED, [])\n } else if (iso) {\n const [, year, month, day, hour, minute, second, zone] = iso\n edits.set(DATE_CREATED, [`${year}${month}${day}`])\n // No zone recorded, none invented: the time is written without one.\n const offset = zone === 'Z' ? '+0000' : (zone?.replace(':', '') ?? '')\n edits.set(\n TIME_CREATED,\n hour ? [`${hour}${minute}${second ?? '00'}${offset}`] : [],\n )\n }\n }\n if (!edits.size) return iim\n\n let list = datasets.slice()\n const values = [...edits.values()].flat()\n if (!declaresUtf8(list) && values.some((v) => !isAscii(v))) {\n list = list.map((d) =>\n d.record === 2 &&\n !BINARY_DATASETS.has(d.dataset) &&\n !edits.has(d.dataset) &&\n !isUtf8(d.data)\n ? { ...d, data: encoder.encode(decodeLegacyText(d.data)), raw: null }\n : d,\n )\n const charset: IimEntry = {\n record: 1,\n dataset: 90,\n data: UTF8_CHARSET,\n raw: null,\n }\n const existing = list.findIndex((d) => d.record === 1 && d.dataset === 90)\n if (existing >= 0) {\n list[existing] = charset\n } else {\n if (!list.some((d) => d.record === 1)) {\n // IIM 4.2 §5: 1:00 ModelVersion is mandatory in the envelope.\n list.splice(sortedIndex(list, 1, 0), 0, {\n record: 1,\n dataset: 0,\n data: RECORD_VERSION,\n raw: null,\n })\n }\n list.splice(sortedIndex(list, 1, 90), 0, charset)\n }\n }\n\n for (const [dataset, texts] of [...edits].sort((a, b) => a[0] - b[0])) {\n const first = list.findIndex((d) => d.record === 2 && d.dataset === dataset)\n const kept = list.filter((d) => !(d.record === 2 && d.dataset === dataset))\n const fresh = texts.map((text): IimEntry => ({\n record: 2,\n dataset,\n data: encoder.encode(text),\n raw: null,\n }))\n kept.splice(first >= 0 ? first : sortedIndex(kept, 2, dataset), 0, ...fresh)\n list = kept\n }\n if (values.length && !list.some((d) => d.record === 2 && d.dataset === 0)) {\n // IIM 4.2 §6: 2:00 RecordVersion is mandatory in the application record.\n list.splice(sortedIndex(list, 2, 0), 0, {\n record: 2,\n dataset: 0,\n data: RECORD_VERSION,\n raw: null,\n })\n }\n\n // Zero padding after the last dataset is dropped (a container pads again\n // as it needs to); anything else there is kept as found.\n const tail = rest.some((byte) => byte !== 0) ? rest : new Uint8Array()\n const out = concat([\n ...list.map((d) => d.raw ?? encodeDataset(d.record, d.dataset, d.data)),\n tail,\n ])\n return sameBytes(out, iim) ? iim : out\n}\n\n// ---------------------------------------------------------------------------\n// Photoshop image resources\n// ---------------------------------------------------------------------------\n\ninterface Resource {\n signature: string\n id: number\n /** The Pascal name as stored: length byte, text, pad to even. */\n name: Uint8Array\n data: Uint8Array\n /** The whole resource as stored, padding included. */\n raw: Uint8Array\n}\n\nfunction parseIrb(irb: Uint8Array): {\n resources: Resource[]\n rest: Uint8Array\n} {\n const resources: Resource[] = []\n let at = 0\n while (at + 12 <= irb.length) {\n const signature = String.fromCharCode(...irb.subarray(at, at + 4))\n if (!IRB_SIGNATURES.has(signature)) break\n const nameLength = irb[at + 6] ?? 0\n const nameEnd = at + 6 + ((nameLength + 2) & ~1)\n if (nameEnd + 4 > irb.length) break\n const size =\n (irb[nameEnd] ?? 0) * 0x1000000 +\n (((irb[nameEnd + 1] ?? 0) << 16) |\n ((irb[nameEnd + 2] ?? 0) << 8) |\n (irb[nameEnd + 3] ?? 0))\n const dataAt = nameEnd + 4\n if (dataAt + size > irb.length) break\n const end = Math.min(dataAt + size + (size % 2), irb.length)\n resources.push({\n signature,\n id: ((irb[at + 4] ?? 0) << 8) | (irb[at + 5] ?? 0),\n name: irb.subarray(at + 6, nameEnd),\n data: irb.subarray(dataAt, dataAt + size),\n raw: irb.subarray(at, end),\n })\n at = end\n }\n return { resources, rest: irb.subarray(at) }\n}\n\nfunction encodeResource(\n resource: Pick<Resource, 'signature' | 'id' | 'name'>,\n data: Uint8Array,\n): Uint8Array {\n const n = data.length\n return concat([\n [...resource.signature].map((c) => c.charCodeAt(0)),\n [resource.id >> 8, resource.id & 0xff],\n resource.name,\n [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff],\n data,\n n % 2 ? [0] : [],\n ])\n}\n\nconst isIptcResource = (r: Resource) =>\n r.signature === '8BIM' && r.id === IPTC_RESOURCE\n\n/** The IIM held in an image resource block (resource 0x0404), or null. */\nexport function irbIim(irb: Uint8Array): Uint8Array | null {\n try {\n return parseIrb(irb).resources.find(isIptcResource)?.data ?? null\n } catch {\n return null\n }\n}\n\n/** Whether an image resource block holds IPTC (resource 0x0404). */\nexport function irbHasIptc(irb: Uint8Array): boolean {\n return irbIim(irb) !== null\n}\n\n/** Read the IPTC of an image resource block. Never throws. */\nexport function readIrbIptc(irb: Uint8Array): EmbeddedCandidate[] {\n const iim = irbIim(irb)\n return iim ? readIim(iim) : []\n}\n\n/**\n * Put `iim` in resource 0x0404, or take the resource out when `iim` is\n * null, and bring the 0x0425 digest in step (set to the new IIM's MD5, or\n * removed with it). A block without 0x0404 gets one at the end. Every other\n * resource, and anything after the last one, is copied byte for byte.\n */\nexport function replaceIrbIim(\n irb: Uint8Array,\n iim: Uint8Array | null,\n): Uint8Array {\n const { resources, rest } = parseIrb(irb)\n const digest = iim\n ? new Uint8Array(createHash('md5').update(iim).digest())\n : null\n const parts: Uint8Array[] = []\n let replaced = false\n for (const resource of resources) {\n if (isIptcResource(resource) && !replaced) {\n replaced = true\n if (iim) parts.push(encodeResource(resource, iim))\n } else if (\n resource.signature === '8BIM' &&\n resource.id === IPTC_DIGEST_RESOURCE\n ) {\n if (digest) parts.push(encodeResource(resource, digest))\n } else {\n parts.push(resource.raw)\n }\n }\n if (!replaced && iim) {\n parts.push(\n encodeResource(\n { signature: '8BIM', id: IPTC_RESOURCE, name: new Uint8Array(2) },\n iim,\n ),\n )\n }\n const out = concat([...parts, rest])\n return sameBytes(out, irb) ? irb : out\n}\n\n/**\n * Write a patch into the IPTC of an image resource block. A block without\n * IPTC is returned as is: new values go to XMP, never into a new IIM (the\n * Metadata Working Group's rule).\n */\nexport function writeIrbIptc(\n irb: Uint8Array,\n patch: EmbeddedPatch,\n): Uint8Array {\n const iim = irbIim(irb)\n if (!iim) return irb\n const next = writeIim(iim, patch)\n return next === iim ? irb : replaceIrbIim(irb, next)\n}\n"],"names":["createHash","decodeLegacyText","isUtf8","FIELDS","key","dataset","repeatable","DATE_CREATED","TIME_CREATED","BINARY_DATASETS","Set","UTF8_CHARSET","Uint8Array","RECORD_VERSION","IRB_SIGNATURES","IPTC_RESOURCE","IPTC_DIGEST_RESOURCE","ISO_DATE","utf8Lenient","TextDecoder","encoder","TextEncoder","concat","parts","out","reduce","n","part","length","at","set","sameBytes","a","b","i","isAscii","text","charCodeAt","parseIim","iim","datasets","size","header","bytes","push","record","data","subarray","raw","rest","encodeDataset","declaresUtf8","d","charset","find","includes","iimEntryText","utf8","decode","nul","indexOf","slice","trim","iptcDate","date","time","exec","year","month","day","iso","t","hour","minute","second","sign","zoneHour","zoneMinute","zone","trimIimPadding","every","byte","readIim","texts","filter","map","Boolean","field","first","more","undefined","value","source","created","iimEntryValues","items","Array","isArray","item","join","sortedIndex","list","index","findIndex","writeIim","patch","patched","Object","prototype","hasOwnProperty","call","edits","Map","offset","replace","values","flat","some","v","has","encode","existing","splice","sort","kept","fresh","tail","parseIrb","irb","resources","signature","String","fromCharCode","nameLength","nameEnd","dataAt","end","Math","min","id","name","encodeResource","resource","c","isIptcResource","r","irbIim","irbHasIptc","readIrbIptc","replaceIrbIim","digest","update","replaced","writeIrbIptc","next"],"mappings":"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"}
@@ -0,0 +1,43 @@
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 JPEG. Returns `null` when the bytes are not
20
+ * a JPEG (no SOI); a damaged or truncated header yields whatever blocks came
21
+ * before the damage.
22
+ *
23
+ * The first EXIF and first main-XMP segment win, as every reader does; all
24
+ * Photoshop APP13 payloads are concatenated in file order.
25
+ */
26
+ export declare function readJpegBlocks(bytes: Uint8Array): ImageBlocks | null;
27
+ /**
28
+ * Writes an update into a JPEG and returns the new file. Only the header's
29
+ * metadata segments change; every other segment and the whole of SOS→EOI
30
+ * and beyond are copied byte for byte.
31
+ *
32
+ * - A block that exists is replaced in place (and any later duplicates of
33
+ * it removed); `null` removes every copy.
34
+ * - A new block goes to its standard position: EXIF right after SOI and a
35
+ * leading APP0 JFIF/JFXX run, XMP after EXIF, APP13 after XMP.
36
+ * - Extended XMP, ICC, COM and every other segment are never touched.
37
+ *
38
+ * Throws `EmbeddedWriteError` for a file that is not a JPEG or whose header
39
+ * is truncated, for a block too large for its segment (EXIF over 65527
40
+ * bytes, XMP over 65502), for an MPF index it cannot keep pointing at the
41
+ * right bytes, and for a PNG text update.
42
+ */
43
+ export declare function writeJpegBlocks(bytes: Uint8Array, update: ImageBlockUpdate): Uint8Array;