@aglyn/aglyn 1.0.0-beta.198 → 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 (77) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/docs-index.generated.js +1 -1
  3. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  4. package/src/lib/app-utils/media-embedded-fields.d.ts +471 -0
  5. package/src/lib/app-utils/media-embedded-fields.js +619 -0
  6. package/src/lib/app-utils/media-embedded-fields.js.map +1 -0
  7. package/src/lib/app-utils/media-embedded-metadata/exif.d.ts +160 -0
  8. package/src/lib/app-utils/media-embedded-metadata/exif.js +1267 -0
  9. package/src/lib/app-utils/media-embedded-metadata/exif.js.map +1 -0
  10. package/src/lib/app-utils/media-embedded-metadata/gif.d.ts +44 -0
  11. package/src/lib/app-utils/media-embedded-metadata/gif.js +134 -0
  12. package/src/lib/app-utils/media-embedded-metadata/gif.js.map +1 -0
  13. package/src/lib/app-utils/media-embedded-metadata/heif.d.ts +23 -0
  14. package/src/lib/app-utils/media-embedded-metadata/heif.js +354 -0
  15. package/src/lib/app-utils/media-embedded-metadata/heif.js.map +1 -0
  16. package/src/lib/app-utils/media-embedded-metadata/icc.d.ts +22 -0
  17. package/src/lib/app-utils/media-embedded-metadata/icc.js +85 -0
  18. package/src/lib/app-utils/media-embedded-metadata/icc.js.map +1 -0
  19. package/src/lib/app-utils/media-embedded-metadata/image-blocks.d.ts +145 -0
  20. package/src/lib/app-utils/media-embedded-metadata/image-blocks.js +200 -0
  21. package/src/lib/app-utils/media-embedded-metadata/image-blocks.js.map +1 -0
  22. package/src/lib/app-utils/media-embedded-metadata/index.d.ts +93 -0
  23. package/src/lib/app-utils/media-embedded-metadata/index.js +744 -0
  24. package/src/lib/app-utils/media-embedded-metadata/index.js.map +1 -0
  25. package/src/lib/app-utils/media-embedded-metadata/iptc.d.ts +59 -0
  26. package/src/lib/app-utils/media-embedded-metadata/iptc.js +529 -0
  27. package/src/lib/app-utils/media-embedded-metadata/iptc.js.map +1 -0
  28. package/src/lib/app-utils/media-embedded-metadata/jpeg.d.ts +43 -0
  29. package/src/lib/app-utils/media-embedded-metadata/jpeg.js +456 -0
  30. package/src/lib/app-utils/media-embedded-metadata/jpeg.js.map +1 -0
  31. package/src/lib/app-utils/media-embedded-metadata/mp4.d.ts +37 -0
  32. package/src/lib/app-utils/media-embedded-metadata/mp4.js +621 -0
  33. package/src/lib/app-utils/media-embedded-metadata/mp4.js.map +1 -0
  34. package/src/lib/app-utils/media-embedded-metadata/ooxml.d.ts +131 -0
  35. package/src/lib/app-utils/media-embedded-metadata/ooxml.js +1058 -0
  36. package/src/lib/app-utils/media-embedded-metadata/ooxml.js.map +1 -0
  37. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.d.ts +223 -0
  38. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js +936 -0
  39. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js.map +1 -0
  40. package/src/lib/app-utils/media-embedded-metadata/pdf.d.ts +82 -0
  41. package/src/lib/app-utils/media-embedded-metadata/pdf.js +1167 -0
  42. package/src/lib/app-utils/media-embedded-metadata/pdf.js.map +1 -0
  43. package/src/lib/app-utils/media-embedded-metadata/png.d.ts +41 -0
  44. package/src/lib/app-utils/media-embedded-metadata/png.js +512 -0
  45. package/src/lib/app-utils/media-embedded-metadata/png.js.map +1 -0
  46. package/src/lib/app-utils/media-embedded-metadata/svg.d.ts +30 -0
  47. package/src/lib/app-utils/media-embedded-metadata/svg.js +154 -0
  48. package/src/lib/app-utils/media-embedded-metadata/svg.js.map +1 -0
  49. package/src/lib/app-utils/media-embedded-metadata/tiff.d.ts +40 -0
  50. package/src/lib/app-utils/media-embedded-metadata/tiff.js +158 -0
  51. package/src/lib/app-utils/media-embedded-metadata/tiff.js.map +1 -0
  52. package/src/lib/app-utils/media-embedded-metadata/types.d.ts +60 -0
  53. package/src/lib/app-utils/media-embedded-metadata/types.js +32 -0
  54. package/src/lib/app-utils/media-embedded-metadata/types.js.map +1 -0
  55. package/src/lib/app-utils/media-embedded-metadata/webm.d.ts +26 -0
  56. package/src/lib/app-utils/media-embedded-metadata/webm.js +417 -0
  57. package/src/lib/app-utils/media-embedded-metadata/webm.js.map +1 -0
  58. package/src/lib/app-utils/media-embedded-metadata/webp.d.ts +37 -0
  59. package/src/lib/app-utils/media-embedded-metadata/webp.js +272 -0
  60. package/src/lib/app-utils/media-embedded-metadata/webp.js.map +1 -0
  61. package/src/lib/app-utils/media-embedded-metadata/xml.d.ts +231 -0
  62. package/src/lib/app-utils/media-embedded-metadata/xml.js +598 -0
  63. package/src/lib/app-utils/media-embedded-metadata/xml.js.map +1 -0
  64. package/src/lib/app-utils/media-embedded-metadata/xmp.d.ts +100 -0
  65. package/src/lib/app-utils/media-embedded-metadata/xmp.js +1475 -0
  66. package/src/lib/app-utils/media-embedded-metadata/xmp.js.map +1 -0
  67. package/src/lib/app-utils/media-embedded-metadata/zip.d.ts +93 -0
  68. package/src/lib/app-utils/media-embedded-metadata/zip.js +531 -0
  69. package/src/lib/app-utils/media-embedded-metadata/zip.js.map +1 -0
  70. package/src/lib/app-utils/name-search.d.ts +12 -0
  71. package/src/lib/app-utils/name-search.js +11 -0
  72. package/src/lib/app-utils/name-search.js.map +1 -1
  73. package/src/lib/foundation/definitions/platform.types.d.ts +11 -0
  74. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  75. package/src/server.d.ts +1 -0
  76. package/src/server.js +7 -0
  77. package/src/server.js.map +1 -1
@@ -0,0 +1,471 @@
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
+ /**
18
+ * The metadata a file carries INSIDE its own bytes (AGL-3331): EXIF, IPTC
19
+ * and XMP on a photo, the document info of a PDF, the core and custom
20
+ * properties of an Office file, the tags of a video container.
21
+ *
22
+ * ## Why this module is client-safe and the parsers are not
23
+ *
24
+ * The parsers and writers live in `media-embedded-metadata/` and are exposed
25
+ * only through `@aglyn/aglyn/server` — they need `node:zlib` and
26
+ * `node:crypto`, and nothing in a browser ever holds the bytes they read.
27
+ * What the console needs is the SHAPE of what they produced (it is stored on
28
+ * the media document) and the rules for presenting and editing it, which is
29
+ * everything here. Keeping the two apart keeps a PDF parser out of the
30
+ * console bundle.
31
+ *
32
+ * ## Canonical keys
33
+ *
34
+ * A photo's caption can be written in three places at once — XMP
35
+ * `dc:description`, IPTC 2:120 and EXIF `ImageDescription` — and a PDF's in
36
+ * two. The reader folds every spelling of one idea into one canonical key
37
+ * ({@link MEDIA_EMBEDDED_CATALOG}), so the drawer shows ONE "Description"
38
+ * rather than three rows that may or may not agree, and the writer fans an
39
+ * edit back out to every place that already holds it (the Metadata Working
40
+ * Group's rule: never leave a stale copy for the next reader to prefer).
41
+ *
42
+ * Anything a file carries that is not in the catalog — an XMP property from
43
+ * a DAM we have never heard of, a PDF custom property, a PNG text chunk —
44
+ * still surfaces, under a namespaced "other" key ({@link otherEmbeddedKey})
45
+ * that round-trips to the writer unchanged.
46
+ */
47
+ /** Where a value was read from. Several sources can back one field. */
48
+ export type MediaEmbeddedSource = 'xmp' | 'iptc' | 'exif' | 'icc' | 'png' | 'comment' | 'pdf' | 'ooxml' | 'mp4' | 'webm' | 'svg';
49
+ /** How the drawer groups fields, in display order. */
50
+ export type MediaEmbeddedGroup = 'description' | 'rights' | 'location' | 'capture' | 'document' | 'technical' | 'other';
51
+ export declare const MEDIA_EMBEDDED_GROUP_ORDER: readonly MediaEmbeddedGroup[];
52
+ export declare const MEDIA_EMBEDDED_GROUP_LABELS: Record<MediaEmbeddedGroup, string>;
53
+ /**
54
+ * The container a file was read as. `null`-free: a file whose format has no
55
+ * reader is simply not given an `embeddedMetadata` record at all.
56
+ */
57
+ export type MediaEmbeddedFormat = 'jpeg' | 'png' | 'webp' | 'gif' | 'tiff' | 'heif' | 'svg' | 'pdf' | 'ooxml' | 'mp4' | 'webm';
58
+ /**
59
+ * The formats whose bytes can take an edit without being re-encoded.
60
+ *
61
+ * Every writer here rewrites METADATA containers only — JPEG APP segments,
62
+ * PNG ancillary chunks, WebP RIFF chunks, a TIFF's IFD, a PDF incremental
63
+ * update, an Office package's property parts — and copies the image data,
64
+ * page content and every other part through byte for byte. A format that
65
+ * cannot promise that (a video's `moov` would move its sample offsets; an
66
+ * SVG is sanitized on every ingress and is its own markup) is read-only.
67
+ */
68
+ export declare const MEDIA_EMBEDDED_WRITABLE_FORMATS: ReadonlySet<MediaEmbeddedFormat>;
69
+ /** How a value is typed, which decides the editor and the validation. */
70
+ export type MediaEmbeddedValueKind = 'text' | 'longText' | 'list' | 'date' | 'rating' | 'gps';
71
+ export interface MediaEmbeddedCatalogEntry {
72
+ label: string;
73
+ group: MediaEmbeddedGroup;
74
+ kind: MediaEmbeddedValueKind;
75
+ /**
76
+ * The formats an edit of this key can be written into. Absent means the
77
+ * key is read-only everywhere — a measured fact such as an exposure time
78
+ * or a page count, which describes the bytes rather than annotating them.
79
+ */
80
+ writable?: readonly MediaEmbeddedFormat[];
81
+ /**
82
+ * `gps` only: the one edit offered is removal. Moving a photo's recorded
83
+ * location is not a thing anybody needs from a DAM; taking it OFF a photo
84
+ * before it goes on a public page is.
85
+ */
86
+ removeOnly?: boolean;
87
+ /** Format-specific label, where a format names the idea differently. */
88
+ labels?: Partial<Record<MediaEmbeddedFormat, string>>;
89
+ /** Format-specific group — a PDF's creation date is not a camera fact. */
90
+ groups?: Partial<Record<MediaEmbeddedFormat, MediaEmbeddedGroup>>;
91
+ }
92
+ /**
93
+ * Every canonical key, in display order within its group.
94
+ *
95
+ * ## Value formats every reader and writer agrees on
96
+ *
97
+ * - `date`: ISO 8601 — `2021-05-03T10:11:12`, with `Z` or `±HH:MM` only when
98
+ * the source recorded a zone (EXIF 2.3's `OffsetTimeOriginal`, a PDF
99
+ * date's `+05'00'`). Never invented: a local camera time stays local.
100
+ * - `list`: an array of strings (creators, keywords).
101
+ * - `gps`: `"<lat>,<lon>"` in signed decimal degrees, six places.
102
+ * - `rating`: `"0"`–`"5"`, XMP's scale; `"-1"` is XMP's "rejected".
103
+ * - technical values are display strings already (`1/250`, `f/2.8`,
104
+ * `35 mm`) because nothing edits them.
105
+ */
106
+ export declare const MEDIA_EMBEDDED_CATALOG: {
107
+ readonly title: {
108
+ readonly label: "Title";
109
+ readonly group: "description";
110
+ readonly kind: "text";
111
+ readonly writable: readonly MediaEmbeddedFormat[];
112
+ };
113
+ readonly headline: {
114
+ readonly label: "Headline";
115
+ readonly group: "description";
116
+ readonly kind: "text";
117
+ readonly writable: readonly MediaEmbeddedFormat[];
118
+ };
119
+ readonly description: {
120
+ readonly label: "Description";
121
+ readonly group: "description";
122
+ readonly kind: "longText";
123
+ readonly writable: readonly MediaEmbeddedFormat[];
124
+ readonly labels: {
125
+ readonly pdf: "Subject";
126
+ readonly ooxml: "Comments";
127
+ };
128
+ };
129
+ readonly subject: {
130
+ readonly label: "Subject";
131
+ readonly group: "description";
132
+ readonly kind: "text";
133
+ readonly writable: readonly ["ooxml"];
134
+ };
135
+ readonly keywords: {
136
+ readonly label: "Keywords";
137
+ readonly group: "description";
138
+ readonly kind: "list";
139
+ readonly writable: readonly MediaEmbeddedFormat[];
140
+ };
141
+ readonly category: {
142
+ readonly label: "Category";
143
+ readonly group: "description";
144
+ readonly kind: "text";
145
+ readonly writable: readonly ["ooxml"];
146
+ };
147
+ readonly comment: {
148
+ readonly label: "Comment";
149
+ readonly group: "description";
150
+ readonly kind: "longText";
151
+ readonly writable: readonly ["png"];
152
+ };
153
+ readonly instructions: {
154
+ readonly label: "Instructions";
155
+ readonly group: "description";
156
+ readonly kind: "longText";
157
+ readonly writable: readonly MediaEmbeddedFormat[];
158
+ };
159
+ readonly label: {
160
+ readonly label: "Label";
161
+ readonly group: "description";
162
+ readonly kind: "text";
163
+ readonly writable: readonly MediaEmbeddedFormat[];
164
+ };
165
+ readonly rating: {
166
+ readonly label: "Rating";
167
+ readonly group: "description";
168
+ readonly kind: "rating";
169
+ readonly writable: readonly MediaEmbeddedFormat[];
170
+ };
171
+ readonly creator: {
172
+ readonly label: "Creator";
173
+ readonly group: "rights";
174
+ readonly kind: "list";
175
+ readonly writable: readonly MediaEmbeddedFormat[];
176
+ readonly labels: {
177
+ readonly pdf: "Author";
178
+ readonly ooxml: "Author";
179
+ };
180
+ };
181
+ readonly copyright: {
182
+ readonly label: "Copyright";
183
+ readonly group: "rights";
184
+ readonly kind: "text";
185
+ readonly writable: readonly MediaEmbeddedFormat[];
186
+ };
187
+ readonly credit: {
188
+ readonly label: "Credit line";
189
+ readonly group: "rights";
190
+ readonly kind: "text";
191
+ readonly writable: readonly MediaEmbeddedFormat[];
192
+ };
193
+ readonly source: {
194
+ readonly label: "Source";
195
+ readonly group: "rights";
196
+ readonly kind: "text";
197
+ readonly writable: readonly MediaEmbeddedFormat[];
198
+ };
199
+ readonly usageTerms: {
200
+ readonly label: "Usage terms";
201
+ readonly group: "rights";
202
+ readonly kind: "longText";
203
+ readonly writable: readonly MediaEmbeddedFormat[];
204
+ };
205
+ readonly webStatement: {
206
+ readonly label: "Copyright info URL";
207
+ readonly group: "rights";
208
+ readonly kind: "text";
209
+ readonly writable: readonly MediaEmbeddedFormat[];
210
+ };
211
+ readonly city: {
212
+ readonly label: "City";
213
+ readonly group: "location";
214
+ readonly kind: "text";
215
+ readonly writable: readonly MediaEmbeddedFormat[];
216
+ };
217
+ readonly state: {
218
+ readonly label: "State / province";
219
+ readonly group: "location";
220
+ readonly kind: "text";
221
+ readonly writable: readonly MediaEmbeddedFormat[];
222
+ };
223
+ readonly country: {
224
+ readonly label: "Country";
225
+ readonly group: "location";
226
+ readonly kind: "text";
227
+ readonly writable: readonly MediaEmbeddedFormat[];
228
+ };
229
+ readonly gps: {
230
+ readonly label: "GPS position";
231
+ readonly group: "location";
232
+ readonly kind: "gps";
233
+ readonly writable: readonly MediaEmbeddedFormat[];
234
+ readonly removeOnly: true;
235
+ };
236
+ readonly gpsAltitude: {
237
+ readonly label: "Altitude";
238
+ readonly group: "location";
239
+ readonly kind: "text";
240
+ };
241
+ readonly createdAt: {
242
+ readonly label: "Date taken";
243
+ readonly group: "capture";
244
+ readonly kind: "date";
245
+ readonly writable: readonly MediaEmbeddedFormat[];
246
+ readonly labels: {
247
+ readonly pdf: "Created";
248
+ readonly ooxml: "Created";
249
+ readonly svg: "Created";
250
+ readonly mp4: "Recorded";
251
+ readonly webm: "Recorded";
252
+ };
253
+ readonly groups: {
254
+ readonly pdf: "document";
255
+ readonly ooxml: "document";
256
+ readonly svg: "document";
257
+ };
258
+ };
259
+ readonly make: {
260
+ readonly label: "Camera make";
261
+ readonly group: "capture";
262
+ readonly kind: "text";
263
+ readonly writable: readonly MediaEmbeddedFormat[];
264
+ };
265
+ readonly model: {
266
+ readonly label: "Camera model";
267
+ readonly group: "capture";
268
+ readonly kind: "text";
269
+ readonly writable: readonly MediaEmbeddedFormat[];
270
+ };
271
+ readonly lens: {
272
+ readonly label: "Lens";
273
+ readonly group: "capture";
274
+ readonly kind: "text";
275
+ readonly writable: readonly MediaEmbeddedFormat[];
276
+ };
277
+ readonly exposure: {
278
+ readonly label: "Exposure";
279
+ readonly group: "capture";
280
+ readonly kind: "text";
281
+ };
282
+ readonly aperture: {
283
+ readonly label: "Aperture";
284
+ readonly group: "capture";
285
+ readonly kind: "text";
286
+ };
287
+ readonly iso: {
288
+ readonly label: "ISO";
289
+ readonly group: "capture";
290
+ readonly kind: "text";
291
+ };
292
+ readonly focalLength: {
293
+ readonly label: "Focal length";
294
+ readonly group: "capture";
295
+ readonly kind: "text";
296
+ };
297
+ readonly flash: {
298
+ readonly label: "Flash";
299
+ readonly group: "capture";
300
+ readonly kind: "text";
301
+ };
302
+ readonly modifiedAt: {
303
+ readonly label: "Modified";
304
+ readonly group: "document";
305
+ readonly kind: "date";
306
+ };
307
+ readonly software: {
308
+ readonly label: "Software";
309
+ readonly group: "document";
310
+ readonly kind: "text";
311
+ readonly writable: readonly MediaEmbeddedFormat[];
312
+ readonly labels: {
313
+ readonly pdf: "Created with";
314
+ readonly ooxml: "Application";
315
+ };
316
+ };
317
+ readonly producer: {
318
+ readonly label: "PDF producer";
319
+ readonly group: "document";
320
+ readonly kind: "text";
321
+ readonly writable: readonly ["pdf"];
322
+ };
323
+ readonly lastModifiedBy: {
324
+ readonly label: "Last modified by";
325
+ readonly group: "document";
326
+ readonly kind: "text";
327
+ };
328
+ readonly company: {
329
+ readonly label: "Company";
330
+ readonly group: "document";
331
+ readonly kind: "text";
332
+ };
333
+ readonly pageCount: {
334
+ readonly label: "Pages";
335
+ readonly group: "document";
336
+ readonly kind: "text";
337
+ readonly labels: {
338
+ readonly ooxml: "Pages / slides";
339
+ };
340
+ };
341
+ readonly pdfVersion: {
342
+ readonly label: "PDF version";
343
+ readonly group: "document";
344
+ readonly kind: "text";
345
+ };
346
+ readonly orientation: {
347
+ readonly label: "Orientation";
348
+ readonly group: "technical";
349
+ readonly kind: "text";
350
+ };
351
+ readonly colorProfile: {
352
+ readonly label: "Color profile";
353
+ readonly group: "technical";
354
+ readonly kind: "text";
355
+ };
356
+ readonly encoder: {
357
+ readonly label: "Encoder";
358
+ readonly group: "technical";
359
+ readonly kind: "text";
360
+ };
361
+ };
362
+ export type MediaEmbeddedCanonicalKey = keyof typeof MEDIA_EMBEDDED_CATALOG;
363
+ /**
364
+ * The namespaces an "other" key can come from. The key round-trips from
365
+ * reader to writer verbatim, so its only job is to be unambiguous:
366
+ *
367
+ * - `xmp|<namespace URI>|<local name>` — a simple XMP property outside the
368
+ * catalog. Keyed by URI, not prefix, because two files may bind the same
369
+ * namespace to different prefixes and must still produce one key.
370
+ * - `png|<keyword>` — a PNG `tEXt`/`zTXt`/`iTXt` chunk.
371
+ * - `pdf|<Name>` — a PDF document-info entry (Acrobat's "Custom" tab).
372
+ * - `ooxml|<name>` — an Office custom document property.
373
+ * - `mp4|<atom or freeform name>`, `webm|<TAG>` — video tags, read-only.
374
+ */
375
+ export type MediaEmbeddedOtherNamespace = 'xmp' | 'png' | 'pdf' | 'ooxml' | 'mp4' | 'webm';
376
+ export declare function otherEmbeddedKey(namespace: MediaEmbeddedOtherNamespace, ...parts: string[]): string;
377
+ export declare function parseOtherEmbeddedKey(key: string): {
378
+ namespace: MediaEmbeddedOtherNamespace;
379
+ parts: string[];
380
+ } | null;
381
+ /** One field as stored on the media document and shown in the drawer. */
382
+ export interface MediaEmbeddedField {
383
+ /** A {@link MediaEmbeddedCanonicalKey} or an {@link otherEmbeddedKey}. */
384
+ key: string;
385
+ label: string;
386
+ group: MediaEmbeddedGroup;
387
+ /** Single-valued fields. Absent when `values` is set. */
388
+ value?: string;
389
+ /** List-valued fields (creators, keywords). */
390
+ values?: string[];
391
+ /** Every block the value was found in, strongest first. */
392
+ sources: MediaEmbeddedSource[];
393
+ /** Whether the drawer may offer an edit for it on this file. */
394
+ editable: boolean;
395
+ }
396
+ /**
397
+ * `embeddedMetadata` on a media document (AGL-3331). Server-written only —
398
+ * the Firestore rules lock it like every other fact read from the bytes.
399
+ */
400
+ export interface MediaEmbeddedMetadata {
401
+ /** Bumped when the reader's output changes shape, so a sweep can re-read. */
402
+ version: number;
403
+ format: MediaEmbeddedFormat;
404
+ fields: MediaEmbeddedField[];
405
+ /** Whether an edit can be written back into this file at all. */
406
+ writable: boolean;
407
+ /** Why not, in a sentence for the drawer, when `writable` is false. */
408
+ readOnlyReason?: string;
409
+ /** True when fields were dropped to stay inside the stored caps. */
410
+ truncated?: boolean;
411
+ /**
412
+ * The `contentSha256` of the bytes this was read from. A record whose
413
+ * digest no longer matches the document's is stale — the file was
414
+ * replaced by a path that did not re-read it — and is re-read on open.
415
+ */
416
+ contentSha256?: string;
417
+ }
418
+ /** The reader's output version. See {@link MediaEmbeddedMetadata.version}. */
419
+ export declare const MEDIA_EMBEDDED_METADATA_VERSION = 1;
420
+ /** Stored caps: one media document must never approach Firestore's 1 MiB. */
421
+ export declare const MEDIA_EMBEDDED_MAX_FIELDS = 150;
422
+ export declare const MEDIA_EMBEDDED_MAX_VALUE_LENGTH = 2000;
423
+ export declare const MEDIA_EMBEDDED_MAX_LIST_ITEMS = 100;
424
+ /** A single keyword or creator; IPTC's own limit for keywords is 64 bytes. */
425
+ export declare const MEDIA_EMBEDDED_MAX_LIST_ITEM_LENGTH = 256;
426
+ /**
427
+ * An edit to a file's embedded metadata: key → new value, or `null` to
428
+ * remove it from the file. A list-valued key takes an array.
429
+ */
430
+ export type MediaEmbeddedPatch = Record<string, string | string[] | null>;
431
+ export declare function isCanonicalEmbeddedKey(key: string): key is MediaEmbeddedCanonicalKey;
432
+ /** The label a key carries on a given format. */
433
+ export declare function embeddedFieldLabel(key: string, format: MediaEmbeddedFormat, fallback?: string): string;
434
+ /** The group a key is shown under on a given format. */
435
+ export declare function embeddedFieldGroup(key: string, format: MediaEmbeddedFormat): MediaEmbeddedGroup;
436
+ /** How a key's value is edited. Other keys are free text. */
437
+ export declare function embeddedFieldKind(key: string): MediaEmbeddedValueKind;
438
+ /**
439
+ * Whether an edit of `key` can be written into a file of `format`.
440
+ *
441
+ * The one place the answer lives: the reader stamps `editable` with it, the
442
+ * drawer offers the "add a field" choices from it, and the write route
443
+ * refuses anything it says no to — so the three cannot disagree about which
444
+ * fields a JPEG can take.
445
+ */
446
+ export declare function embeddedKeyWritable(key: string, format: MediaEmbeddedFormat): boolean;
447
+ /**
448
+ * The canonical keys a file of this format can take, whether or not it
449
+ * holds them yet — what the drawer offers under "Add a field".
450
+ */
451
+ export declare function embeddedWritableKeys(format: MediaEmbeddedFormat): MediaEmbeddedCanonicalKey[];
452
+ /**
453
+ * Validate an edit before it reaches a writer, or say why it cannot.
454
+ *
455
+ * Runs in the drawer (to explain) and in the write route (to refuse), so
456
+ * the messages are written for the person who typed the value.
457
+ */
458
+ export declare function sanitizeEmbeddedPatch(format: MediaEmbeddedFormat, patch: unknown): {
459
+ patch: MediaEmbeddedPatch;
460
+ } | {
461
+ error: string;
462
+ };
463
+ /** The value of a field as one line of text. */
464
+ export declare function embeddedFieldText(field: MediaEmbeddedField): string;
465
+ export declare function embeddedMetadataReadable(contentType: unknown): boolean;
466
+ /**
467
+ * A field's value as the drawer shows it — the stored value is the
468
+ * machine form every writer agrees on, and this is the only place it is
469
+ * dressed for a person.
470
+ */
471
+ export declare function embeddedFieldDisplay(field: Pick<MediaEmbeddedField, 'key' | 'value' | 'values'>, locale?: string): string;