@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,154 @@
|
|
|
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 title, description and XMP of an SVG (AGL-3331). Read-only: an SVG is
|
|
18
|
+
* sanitized on every ingress and is its own markup, so an edit belongs in
|
|
19
|
+
* the file's source, not in a metadata writer.
|
|
20
|
+
*
|
|
21
|
+
* - `<title>` and `<desc>` that are DIRECT children of the root `<svg>` are
|
|
22
|
+
* the document's own (SVG 2 §5.9); a `<title>` inside a `<g>` names that
|
|
23
|
+
* group, not the file, and is ignored.
|
|
24
|
+
* - A `<metadata>` child holding an `rdf:RDF` — how Inkscape and Illustrator
|
|
25
|
+
* embed Dublin Core and XMP (SVG 1.1 §21.2) — is handed back as `xmp`
|
|
26
|
+
* for the XMP reader, with every namespace declared on its ancestors
|
|
27
|
+
* copied onto it, since Inkscape binds `rdf:`, `dc:` and `cc:` on the root
|
|
28
|
+
* `<svg>` and the fragment would otherwise not parse on its own.
|
|
29
|
+
*
|
|
30
|
+
* The document is streamed through `scanXml`, never built into a tree, so a
|
|
31
|
+
* 15 MB map export costs one pass and no more memory than its nesting. A
|
|
32
|
+
* DOCTYPE is tolerated only without an internal subset — Illustrator has
|
|
33
|
+
* written an inert external-id DOCTYPE for twenty years, and the SVG
|
|
34
|
+
* sanitizer keeps it for that reason — while an internal subset or any
|
|
35
|
+
* `<!ENTITY>` makes the file unreadable here.
|
|
36
|
+
*/ import { escapeXmlAttribute, scanXml } from "./ooxml.js";
|
|
37
|
+
const NS_SVG = 'http://www.w3.org/2000/svg';
|
|
38
|
+
const NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
|
|
39
|
+
const NS_XMLNS = 'http://www.w3.org/2000/xmlns/';
|
|
40
|
+
/** Past this, the file is not read at all. */ export const SVG_READ_MAX_BYTES = 16 * 1024 * 1024;
|
|
41
|
+
function decode(bytes) {
|
|
42
|
+
if (bytes[0] === 0xfe && bytes[1] === 0xff) return new TextDecoder('utf-16be').decode(bytes);
|
|
43
|
+
if (bytes[0] === 0xff && bytes[1] === 0xfe) return new TextDecoder('utf-16le').decode(bytes);
|
|
44
|
+
return new TextDecoder('utf-8').decode(bytes);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* An element's source text, standalone: every namespace binding in scope
|
|
48
|
+
* at it that it does not declare itself is added to its start tag.
|
|
49
|
+
*/ function standalone(source, tag, end) {
|
|
50
|
+
const own = new Set(tag.attributes.filter((attribute)=>attribute.ns === NS_XMLNS).map((attribute)=>attribute.prefix === null ? '' : attribute.local));
|
|
51
|
+
let declarations = '';
|
|
52
|
+
for (const [prefix, uri] of tag.namespaces){
|
|
53
|
+
if (prefix === 'xml' || own.has(prefix)) continue;
|
|
54
|
+
declarations += ` ${prefix ? `xmlns:${prefix}` : 'xmlns'}="${escapeXmlAttribute(uri)}"`;
|
|
55
|
+
}
|
|
56
|
+
const at = tag.start + 1 + tag.name.length;
|
|
57
|
+
return source.slice(tag.start, at) + declarations + source.slice(at, end);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read an SVG's title, description and embedded XMP.
|
|
61
|
+
*
|
|
62
|
+
* Null when the bytes are over {@link SVG_READ_MAX_BYTES}, are not a
|
|
63
|
+
* well-formed XML document whose root is `<svg>`, or carry a DOCTYPE
|
|
64
|
+
* internal subset or an entity declaration.
|
|
65
|
+
*/ export function readSvg(bytes) {
|
|
66
|
+
if (bytes.length > SVG_READ_MAX_BYTES) return null;
|
|
67
|
+
const source = decode(bytes);
|
|
68
|
+
// Held in one object: the handlers below assign it, and a closure's
|
|
69
|
+
// assignments are invisible to the narrowing of plain `let` variables.
|
|
70
|
+
const state = {
|
|
71
|
+
root: null,
|
|
72
|
+
isSvg: false,
|
|
73
|
+
title: null,
|
|
74
|
+
description: null,
|
|
75
|
+
capture: null,
|
|
76
|
+
metadata: null,
|
|
77
|
+
packet: null,
|
|
78
|
+
xmp: null
|
|
79
|
+
};
|
|
80
|
+
const ok = scanXml(source, {
|
|
81
|
+
open (tag) {
|
|
82
|
+
if (tag.depth === 0) {
|
|
83
|
+
state.root = tag;
|
|
84
|
+
state.isSvg = tag.local === 'svg' && (tag.ns === null || tag.ns === NS_SVG);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const root = state.root;
|
|
88
|
+
if (!state.isSvg || !root) return;
|
|
89
|
+
if (tag.depth === 1) {
|
|
90
|
+
if (tag.ns !== root.ns) return;
|
|
91
|
+
if (tag.local === 'title' && !state.title || tag.local === 'desc' && !state.description) {
|
|
92
|
+
state.capture = {
|
|
93
|
+
tag,
|
|
94
|
+
text: []
|
|
95
|
+
};
|
|
96
|
+
} else if (tag.local === 'metadata' && state.xmp === null) {
|
|
97
|
+
state.metadata = tag;
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (!state.metadata) return;
|
|
102
|
+
const isRdf = tag.local === 'RDF' && tag.ns === NS_RDF;
|
|
103
|
+
if (tag.depth === 2) state.packet = {
|
|
104
|
+
tag,
|
|
105
|
+
hasRdf: isRdf
|
|
106
|
+
};
|
|
107
|
+
else if (state.packet && isRdf) state.packet.hasRdf = true;
|
|
108
|
+
},
|
|
109
|
+
text (text) {
|
|
110
|
+
var _state_capture;
|
|
111
|
+
(_state_capture = state.capture) == null ? void 0 : _state_capture.text.push(text);
|
|
112
|
+
},
|
|
113
|
+
close (tag, _closeStart, end) {
|
|
114
|
+
var _state_capture, _state_packet;
|
|
115
|
+
if (((_state_capture = state.capture) == null ? void 0 : _state_capture.tag) === tag) {
|
|
116
|
+
const value = state.capture.text.join('');
|
|
117
|
+
if (tag.local === 'title') state.title = value.replace(/\s+/g, ' ').trim();
|
|
118
|
+
else state.description = value.trim();
|
|
119
|
+
state.capture = null;
|
|
120
|
+
} else if (((_state_packet = state.packet) == null ? void 0 : _state_packet.tag) === tag) {
|
|
121
|
+
if (state.packet.hasRdf && state.xmp === null) {
|
|
122
|
+
state.xmp = standalone(source, tag, end);
|
|
123
|
+
}
|
|
124
|
+
state.packet = null;
|
|
125
|
+
} else if (state.metadata === tag) {
|
|
126
|
+
state.metadata = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}, {
|
|
130
|
+
allowExternalDoctype: true,
|
|
131
|
+
maxDepth: 1024,
|
|
132
|
+
maxElements: 8000000
|
|
133
|
+
});
|
|
134
|
+
if (!ok || !state.isSvg) return null;
|
|
135
|
+
const candidates = [];
|
|
136
|
+
if (state.title) candidates.push({
|
|
137
|
+
key: 'title',
|
|
138
|
+
value: state.title,
|
|
139
|
+
source: 'svg'
|
|
140
|
+
});
|
|
141
|
+
if (state.description) {
|
|
142
|
+
candidates.push({
|
|
143
|
+
key: 'description',
|
|
144
|
+
value: state.description,
|
|
145
|
+
source: 'svg'
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
candidates,
|
|
150
|
+
xmp: state.xmp
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
//# sourceMappingURL=svg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/aglyn/src/lib/app-utils/media-embedded-metadata/svg.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 title, description and XMP of an SVG (AGL-3331). Read-only: an SVG is\n * sanitized on every ingress and is its own markup, so an edit belongs in\n * the file's source, not in a metadata writer.\n *\n * - `<title>` and `<desc>` that are DIRECT children of the root `<svg>` are\n * the document's own (SVG 2 §5.9); a `<title>` inside a `<g>` names that\n * group, not the file, and is ignored.\n * - A `<metadata>` child holding an `rdf:RDF` — how Inkscape and Illustrator\n * embed Dublin Core and XMP (SVG 1.1 §21.2) — is handed back as `xmp`\n * for the XMP reader, with every namespace declared on its ancestors\n * copied onto it, since Inkscape binds `rdf:`, `dc:` and `cc:` on the root\n * `<svg>` and the fragment would otherwise not parse on its own.\n *\n * The document is streamed through `scanXml`, never built into a tree, so a\n * 15 MB map export costs one pass and no more memory than its nesting. A\n * DOCTYPE is tolerated only without an internal subset — Illustrator has\n * written an inert external-id DOCTYPE for twenty years, and the SVG\n * sanitizer keeps it for that reason — while an internal subset or any\n * `<!ENTITY>` makes the file unreadable here.\n */\n\nimport { escapeXmlAttribute, scanXml, type XmlStartTag } from './ooxml'\nimport type { EmbeddedCandidate } from './types'\n\nconst NS_SVG = 'http://www.w3.org/2000/svg'\nconst NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'\nconst NS_XMLNS = 'http://www.w3.org/2000/xmlns/'\n\n/** Past this, the file is not read at all. */\nexport const SVG_READ_MAX_BYTES = 16 * 1024 * 1024\n\nfunction decode(bytes: Uint8Array): string {\n if (bytes[0] === 0xfe && bytes[1] === 0xff)\n return new TextDecoder('utf-16be').decode(bytes)\n if (bytes[0] === 0xff && bytes[1] === 0xfe)\n return new TextDecoder('utf-16le').decode(bytes)\n return new TextDecoder('utf-8').decode(bytes)\n}\n\n/**\n * An element's source text, standalone: every namespace binding in scope\n * at it that it does not declare itself is added to its start tag.\n */\nfunction standalone(source: string, tag: XmlStartTag, end: number): string {\n const own = new Set(\n tag.attributes\n .filter((attribute) => attribute.ns === NS_XMLNS)\n .map((attribute) => (attribute.prefix === null ? '' : attribute.local)),\n )\n let declarations = ''\n for (const [prefix, uri] of tag.namespaces) {\n if (prefix === 'xml' || own.has(prefix)) continue\n declarations += ` ${prefix ? `xmlns:${prefix}` : 'xmlns'}=\"${escapeXmlAttribute(uri)}\"`\n }\n const at = tag.start + 1 + tag.name.length\n return source.slice(tag.start, at) + declarations + source.slice(at, end)\n}\n\n/**\n * Read an SVG's title, description and embedded XMP.\n *\n * Null when the bytes are over {@link SVG_READ_MAX_BYTES}, are not a\n * well-formed XML document whose root is `<svg>`, or carry a DOCTYPE\n * internal subset or an entity declaration.\n */\nexport function readSvg(\n bytes: Uint8Array,\n): { candidates: EmbeddedCandidate[]; xmp: string | null } | null {\n if (bytes.length > SVG_READ_MAX_BYTES) return null\n const source = decode(bytes)\n\n // Held in one object: the handlers below assign it, and a closure's\n // assignments are invisible to the narrowing of plain `let` variables.\n const state: {\n root: XmlStartTag | null\n isSvg: boolean\n title: string | null\n description: string | null\n capture: { tag: XmlStartTag; text: string[] } | null\n metadata: XmlStartTag | null\n packet: { tag: XmlStartTag; hasRdf: boolean } | null\n xmp: string | null\n } = {\n root: null,\n isSvg: false,\n title: null,\n description: null,\n capture: null,\n metadata: null,\n packet: null,\n xmp: null,\n }\n\n const ok = scanXml(\n source,\n {\n open(tag) {\n if (tag.depth === 0) {\n state.root = tag\n state.isSvg =\n tag.local === 'svg' && (tag.ns === null || tag.ns === NS_SVG)\n return\n }\n const root = state.root\n if (!state.isSvg || !root) return\n if (tag.depth === 1) {\n if (tag.ns !== root.ns) return\n if (\n (tag.local === 'title' && !state.title) ||\n (tag.local === 'desc' && !state.description)\n ) {\n state.capture = { tag, text: [] }\n } else if (tag.local === 'metadata' && state.xmp === null) {\n state.metadata = tag\n }\n return\n }\n if (!state.metadata) return\n const isRdf = tag.local === 'RDF' && tag.ns === NS_RDF\n if (tag.depth === 2) state.packet = { tag, hasRdf: isRdf }\n else if (state.packet && isRdf) state.packet.hasRdf = true\n },\n text(text) {\n state.capture?.text.push(text)\n },\n close(tag, _closeStart, end) {\n if (state.capture?.tag === tag) {\n const value = state.capture.text.join('')\n if (tag.local === 'title')\n state.title = value.replace(/\\s+/g, ' ').trim()\n else state.description = value.trim()\n state.capture = null\n } else if (state.packet?.tag === tag) {\n if (state.packet.hasRdf && state.xmp === null) {\n state.xmp = standalone(source, tag, end)\n }\n state.packet = null\n } else if (state.metadata === tag) {\n state.metadata = null\n }\n },\n },\n { allowExternalDoctype: true, maxDepth: 1024, maxElements: 8_000_000 },\n )\n if (!ok || !state.isSvg) return null\n\n const candidates: EmbeddedCandidate[] = []\n if (state.title)\n candidates.push({ key: 'title', value: state.title, source: 'svg' })\n if (state.description) {\n candidates.push({\n key: 'description',\n value: state.description,\n source: 'svg',\n })\n }\n return { candidates, xmp: state.xmp }\n}\n"],"names":["escapeXmlAttribute","scanXml","NS_SVG","NS_RDF","NS_XMLNS","SVG_READ_MAX_BYTES","decode","bytes","TextDecoder","standalone","source","tag","end","own","Set","attributes","filter","attribute","ns","map","prefix","local","declarations","uri","namespaces","has","at","start","name","length","slice","readSvg","state","root","isSvg","title","description","capture","metadata","packet","xmp","ok","open","depth","text","isRdf","hasRdf","push","close","_closeStart","value","join","replace","trim","allowExternalDoctype","maxDepth","maxElements","candidates","key"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 EmbeddedPatch } from './types';
|
|
18
|
+
/**
|
|
19
|
+
* The XMP packet and the IPTC of a TIFF file, or null when the bytes are
|
|
20
|
+
* not a TIFF. `iim` is the stream without the tag's zero padding, and falls
|
|
21
|
+
* back to the copy inside the Photoshop resource block when tag 33723 is
|
|
22
|
+
* absent. Never throws.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readTiffFile(bytes: Uint8Array): {
|
|
25
|
+
xmp: string | null;
|
|
26
|
+
iim: Uint8Array | null;
|
|
27
|
+
irb: Uint8Array | null;
|
|
28
|
+
} | null;
|
|
29
|
+
/**
|
|
30
|
+
* Write edits into a TIFF file: `exifPatch` to its EXIF tags (existing
|
|
31
|
+
* tags only, as `writeExif`), `xmp` to tag 700 and `iim` to tag 33723 —
|
|
32
|
+
* each added when missing and removed by `null`. When the Photoshop
|
|
33
|
+
* resource block (34377) also holds the IIM, that copy and its digest are
|
|
34
|
+
* kept in step, so no reader is left to prefer a stale one.
|
|
35
|
+
*/
|
|
36
|
+
export declare function writeTiffFile(bytes: Uint8Array, update: {
|
|
37
|
+
exifPatch?: EmbeddedPatch;
|
|
38
|
+
xmp?: string | null;
|
|
39
|
+
iim?: Uint8Array | null;
|
|
40
|
+
}): Uint8Array;
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
* TIFF image files, `image/tiff` (AGL-3331).
|
|
19
|
+
*
|
|
20
|
+
* A TIFF file IS the TIFF structure EXIF borrows, so `readExif(bytes)`
|
|
21
|
+
* reads its EXIF directly and this module only finds the other two blocks,
|
|
22
|
+
* which live as IFD0 tags:
|
|
23
|
+
*
|
|
24
|
+
* - 700 XMP (XMP Specification Part 3 §1.1.4): the packet, as BYTE, UTF-8.
|
|
25
|
+
* - 33723 IPTC-NAA: the raw IIM stream, typed LONG by Photoshop and
|
|
26
|
+
* UNDEFINED by ImageMagick, padded to a multiple of four.
|
|
27
|
+
* - 34377 Photoshop: an image resource block, which in a file from
|
|
28
|
+
* Photoshop carries a second copy of the IIM (0x0404) and its digest.
|
|
29
|
+
*
|
|
30
|
+
* Writing goes through `editTiff` with `allowAdd`, so XMP can be added to a
|
|
31
|
+
* file that has none. Strip and tile offsets are absolute and nothing
|
|
32
|
+
* before the old end of the file moves, so the pixels come through
|
|
33
|
+
* byte-identical; the only bytes that change are IFD entries, the old slot
|
|
34
|
+
* of a replaced value, and a relocated IFD0's old table (zeroed).
|
|
35
|
+
*/ import { TIFF_TYPE, editTiff, isTiff, readTiffTagBytes } from "./exif.js";
|
|
36
|
+
import { irbIim, replaceIrbIim, trimIimPadding } from "./iptc.js";
|
|
37
|
+
import { EmbeddedWriteError } from "./types.js";
|
|
38
|
+
const XMP_TAG = 700;
|
|
39
|
+
const IPTC_TAG = 33723;
|
|
40
|
+
const PHOTOSHOP_TAG = 34377;
|
|
41
|
+
const utf8Lenient = new TextDecoder('utf-8');
|
|
42
|
+
function sameBytes(a, b) {
|
|
43
|
+
if (a.length !== b.length) return false;
|
|
44
|
+
for(let i = 0; i < a.length; i++)if (a[i] !== b[i]) return false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const encoder = new TextEncoder();
|
|
48
|
+
function decodeXmp(bytes) {
|
|
49
|
+
// TextDecoder drops a leading BOM by default (WHATWG Encoding §7.2).
|
|
50
|
+
const text = utf8Lenient.decode(bytes);
|
|
51
|
+
let end = text.length;
|
|
52
|
+
while(end > 0 && text.charCodeAt(end - 1) === 0)end--;
|
|
53
|
+
const packet = text.slice(0, end);
|
|
54
|
+
return packet.trim() ? packet : null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The XMP packet and the IPTC of a TIFF file, or null when the bytes are
|
|
58
|
+
* not a TIFF. `iim` is the stream without the tag's zero padding, and falls
|
|
59
|
+
* back to the copy inside the Photoshop resource block when tag 33723 is
|
|
60
|
+
* absent. Never throws.
|
|
61
|
+
*/ export function readTiffFile(bytes) {
|
|
62
|
+
var _ref;
|
|
63
|
+
var _irbIim;
|
|
64
|
+
if (!isTiff(bytes)) return null;
|
|
65
|
+
const xmp = readTiffTagBytes(bytes, 'ifd0', XMP_TAG);
|
|
66
|
+
const iim = readTiffTagBytes(bytes, 'ifd0', IPTC_TAG);
|
|
67
|
+
const irb = readTiffTagBytes(bytes, 'ifd0', PHOTOSHOP_TAG);
|
|
68
|
+
const irbCopy = irb ? irb.bytes.slice() : null;
|
|
69
|
+
return {
|
|
70
|
+
xmp: xmp ? decodeXmp(xmp.bytes) : null,
|
|
71
|
+
iim: iim ? trimIimPadding(iim.bytes).slice() : irbCopy ? (_ref = (_irbIim = irbIim(irbCopy)) == null ? void 0 : _irbIim.slice()) != null ? _ref : null : null,
|
|
72
|
+
irb: irbCopy
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Tag 33723. An existing tag keeps its own type. A new one is UNDEFINED:
|
|
77
|
+
* a LONG-typed IIM in a big-endian file is byte-swapped by ImageMagick
|
|
78
|
+
* 7.1 (libtiff 4.7) and lost, while UNDEFINED reads back in either order
|
|
79
|
+
* (checked for AGL-3331). The stream is padded to a multiple of four, as
|
|
80
|
+
* Photoshop and ImageMagick write it, so a reader that counts the tag in
|
|
81
|
+
* LONGs still sees all of it.
|
|
82
|
+
*/ function iimWrite(current, iim) {
|
|
83
|
+
const write = {
|
|
84
|
+
ifd: 'ifd0',
|
|
85
|
+
tag: IPTC_TAG
|
|
86
|
+
};
|
|
87
|
+
if (!iim) return _extends({}, write, {
|
|
88
|
+
type: TIFF_TYPE.UNDEFINED,
|
|
89
|
+
value: null
|
|
90
|
+
});
|
|
91
|
+
const kept = [
|
|
92
|
+
TIFF_TYPE.BYTE,
|
|
93
|
+
TIFF_TYPE.UNDEFINED,
|
|
94
|
+
TIFF_TYPE.LONG
|
|
95
|
+
];
|
|
96
|
+
const type = current && kept.includes(current.type) ? current.type : TIFF_TYPE.UNDEFINED;
|
|
97
|
+
const padded = new Uint8Array(Math.ceil(iim.length / 4) * 4);
|
|
98
|
+
padded.set(iim);
|
|
99
|
+
return _extends({}, write, {
|
|
100
|
+
type,
|
|
101
|
+
value: padded
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Write edits into a TIFF file: `exifPatch` to its EXIF tags (existing
|
|
106
|
+
* tags only, as `writeExif`), `xmp` to tag 700 and `iim` to tag 33723 —
|
|
107
|
+
* each added when missing and removed by `null`. When the Photoshop
|
|
108
|
+
* resource block (34377) also holds the IIM, that copy and its digest are
|
|
109
|
+
* kept in step, so no reader is left to prefer a stale one.
|
|
110
|
+
*/ export function writeTiffFile(bytes, update) {
|
|
111
|
+
var _update_xmp;
|
|
112
|
+
if (!isTiff(bytes)) {
|
|
113
|
+
throw new EmbeddedWriteError('This file is not a TIFF image.');
|
|
114
|
+
}
|
|
115
|
+
const writes = [];
|
|
116
|
+
const currentXmp = readTiffTagBytes(bytes, 'ifd0', XMP_TAG);
|
|
117
|
+
// The packet already there, NUL padding aside, is not rewritten.
|
|
118
|
+
const sameXmp = currentXmp !== null && update.xmp !== undefined && decodeXmp(currentXmp.bytes) === (((_update_xmp = update.xmp) == null ? void 0 : _update_xmp.trim()) ? update.xmp : null);
|
|
119
|
+
if (update.xmp !== undefined && !sameXmp) {
|
|
120
|
+
var _update_xmp1;
|
|
121
|
+
const current = currentXmp;
|
|
122
|
+
writes.push({
|
|
123
|
+
ifd: 'ifd0',
|
|
124
|
+
tag: XMP_TAG,
|
|
125
|
+
type: (current == null ? void 0 : current.type) === TIFF_TYPE.UNDEFINED ? TIFF_TYPE.UNDEFINED : TIFF_TYPE.BYTE,
|
|
126
|
+
value: ((_update_xmp1 = update.xmp) == null ? void 0 : _update_xmp1.trim()) ? encoder.encode(update.xmp) : null
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (update.iim !== undefined) {
|
|
130
|
+
var _update_iim;
|
|
131
|
+
const iim = ((_update_iim = update.iim) == null ? void 0 : _update_iim.length) ? trimIimPadding(update.iim) : null;
|
|
132
|
+
const current = readTiffTagBytes(bytes, 'ifd0', IPTC_TAG);
|
|
133
|
+
// The stream already there, padding aside, is not rewritten.
|
|
134
|
+
const sameIim = current !== null && iim !== null && sameBytes(trimIimPadding(current.bytes), iim);
|
|
135
|
+
const irb = readTiffTagBytes(bytes, 'ifd0', PHOTOSHOP_TAG);
|
|
136
|
+
const irbHoldsIim = irb ? irbIim(irb.bytes) !== null : false;
|
|
137
|
+
// 33723 is the TIFF home of IPTC; a file that keeps it only in the
|
|
138
|
+
// resource block has it updated there and nowhere else.
|
|
139
|
+
if ((current || !irbHoldsIim) && !sameIim) {
|
|
140
|
+
writes.push(iimWrite(current, iim));
|
|
141
|
+
}
|
|
142
|
+
if (irb && irbHoldsIim) {
|
|
143
|
+
writes.push({
|
|
144
|
+
ifd: 'ifd0',
|
|
145
|
+
tag: PHOTOSHOP_TAG,
|
|
146
|
+
type: irb.type === TIFF_TYPE.UNDEFINED ? TIFF_TYPE.UNDEFINED : TIFF_TYPE.BYTE,
|
|
147
|
+
value: replaceIrbIim(irb.bytes, iim)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return editTiff(bytes, {
|
|
152
|
+
patch: update.exifPatch,
|
|
153
|
+
writes,
|
|
154
|
+
allowAdd: true
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
//# sourceMappingURL=tiff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/aglyn/src/lib/app-utils/media-embedded-metadata/tiff.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 * TIFF image files, `image/tiff` (AGL-3331).\n *\n * A TIFF file IS the TIFF structure EXIF borrows, so `readExif(bytes)`\n * reads its EXIF directly and this module only finds the other two blocks,\n * which live as IFD0 tags:\n *\n * - 700 XMP (XMP Specification Part 3 §1.1.4): the packet, as BYTE, UTF-8.\n * - 33723 IPTC-NAA: the raw IIM stream, typed LONG by Photoshop and\n * UNDEFINED by ImageMagick, padded to a multiple of four.\n * - 34377 Photoshop: an image resource block, which in a file from\n * Photoshop carries a second copy of the IIM (0x0404) and its digest.\n *\n * Writing goes through `editTiff` with `allowAdd`, so XMP can be added to a\n * file that has none. Strip and tile offsets are absolute and nothing\n * before the old end of the file moves, so the pixels come through\n * byte-identical; the only bytes that change are IFD entries, the old slot\n * of a replaced value, and a relocated IFD0's old table (zeroed).\n */\n\nimport {\n TIFF_TYPE,\n editTiff,\n isTiff,\n readTiffTagBytes,\n type TiffTagWrite,\n} from './exif'\nimport { irbIim, replaceIrbIim, trimIimPadding } from './iptc'\nimport { EmbeddedWriteError, type EmbeddedPatch } from './types'\n\nconst XMP_TAG = 700\nconst IPTC_TAG = 33723\nconst PHOTOSHOP_TAG = 34377\n\nconst utf8Lenient = new TextDecoder('utf-8')\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}\nconst encoder = new TextEncoder()\n\nfunction decodeXmp(bytes: Uint8Array): string | null {\n // TextDecoder drops a leading BOM by default (WHATWG Encoding §7.2).\n const text = utf8Lenient.decode(bytes)\n let end = text.length\n while (end > 0 && text.charCodeAt(end - 1) === 0) end--\n const packet = text.slice(0, end)\n return packet.trim() ? packet : null\n}\n\n/**\n * The XMP packet and the IPTC of a TIFF file, or null when the bytes are\n * not a TIFF. `iim` is the stream without the tag's zero padding, and falls\n * back to the copy inside the Photoshop resource block when tag 33723 is\n * absent. Never throws.\n */\nexport function readTiffFile(bytes: Uint8Array): {\n xmp: string | null\n iim: Uint8Array | null\n irb: Uint8Array | null\n} | null {\n if (!isTiff(bytes)) return null\n const xmp = readTiffTagBytes(bytes, 'ifd0', XMP_TAG)\n const iim = readTiffTagBytes(bytes, 'ifd0', IPTC_TAG)\n const irb = readTiffTagBytes(bytes, 'ifd0', PHOTOSHOP_TAG)\n const irbCopy = irb ? irb.bytes.slice() : null\n return {\n xmp: xmp ? decodeXmp(xmp.bytes) : null,\n iim: iim\n ? trimIimPadding(iim.bytes).slice()\n : irbCopy\n ? (irbIim(irbCopy)?.slice() ?? null)\n : null,\n irb: irbCopy,\n }\n}\n\n/**\n * Tag 33723. An existing tag keeps its own type. A new one is UNDEFINED:\n * a LONG-typed IIM in a big-endian file is byte-swapped by ImageMagick\n * 7.1 (libtiff 4.7) and lost, while UNDEFINED reads back in either order\n * (checked for AGL-3331). The stream is padded to a multiple of four, as\n * Photoshop and ImageMagick write it, so a reader that counts the tag in\n * LONGs still sees all of it.\n */\nfunction iimWrite(\n current: { type: number } | null,\n iim: Uint8Array | null,\n): TiffTagWrite {\n const write = { ifd: 'ifd0' as const, tag: IPTC_TAG }\n if (!iim) return { ...write, type: TIFF_TYPE.UNDEFINED, value: null }\n const kept: number[] = [TIFF_TYPE.BYTE, TIFF_TYPE.UNDEFINED, TIFF_TYPE.LONG]\n const type =\n current && kept.includes(current.type) ? current.type : TIFF_TYPE.UNDEFINED\n const padded = new Uint8Array(Math.ceil(iim.length / 4) * 4)\n padded.set(iim)\n return { ...write, type, value: padded }\n}\n\n/**\n * Write edits into a TIFF file: `exifPatch` to its EXIF tags (existing\n * tags only, as `writeExif`), `xmp` to tag 700 and `iim` to tag 33723 —\n * each added when missing and removed by `null`. When the Photoshop\n * resource block (34377) also holds the IIM, that copy and its digest are\n * kept in step, so no reader is left to prefer a stale one.\n */\nexport function writeTiffFile(\n bytes: Uint8Array,\n update: {\n exifPatch?: EmbeddedPatch\n xmp?: string | null\n iim?: Uint8Array | null\n },\n): Uint8Array {\n if (!isTiff(bytes)) {\n throw new EmbeddedWriteError('This file is not a TIFF image.')\n }\n const writes: TiffTagWrite[] = []\n const currentXmp = readTiffTagBytes(bytes, 'ifd0', XMP_TAG)\n // The packet already there, NUL padding aside, is not rewritten.\n const sameXmp =\n currentXmp !== null &&\n update.xmp !== undefined &&\n decodeXmp(currentXmp.bytes) === (update.xmp?.trim() ? update.xmp : null)\n if (update.xmp !== undefined && !sameXmp) {\n const current = currentXmp\n writes.push({\n ifd: 'ifd0',\n tag: XMP_TAG,\n type:\n current?.type === TIFF_TYPE.UNDEFINED\n ? TIFF_TYPE.UNDEFINED\n : TIFF_TYPE.BYTE,\n value: update.xmp?.trim() ? encoder.encode(update.xmp) : null,\n })\n }\n if (update.iim !== undefined) {\n const iim = update.iim?.length ? trimIimPadding(update.iim) : null\n const current = readTiffTagBytes(bytes, 'ifd0', IPTC_TAG)\n // The stream already there, padding aside, is not rewritten.\n const sameIim =\n current !== null &&\n iim !== null &&\n sameBytes(trimIimPadding(current.bytes), iim)\n const irb = readTiffTagBytes(bytes, 'ifd0', PHOTOSHOP_TAG)\n const irbHoldsIim = irb ? irbIim(irb.bytes) !== null : false\n // 33723 is the TIFF home of IPTC; a file that keeps it only in the\n // resource block has it updated there and nowhere else.\n if ((current || !irbHoldsIim) && !sameIim) {\n writes.push(iimWrite(current, iim))\n }\n if (irb && irbHoldsIim) {\n writes.push({\n ifd: 'ifd0',\n tag: PHOTOSHOP_TAG,\n type:\n irb.type === TIFF_TYPE.UNDEFINED\n ? TIFF_TYPE.UNDEFINED\n : TIFF_TYPE.BYTE,\n value: replaceIrbIim(irb.bytes, iim),\n })\n }\n }\n return editTiff(bytes, { patch: update.exifPatch, writes, allowAdd: true })\n}\n"],"names":["TIFF_TYPE","editTiff","isTiff","readTiffTagBytes","irbIim","replaceIrbIim","trimIimPadding","EmbeddedWriteError","XMP_TAG","IPTC_TAG","PHOTOSHOP_TAG","utf8Lenient","TextDecoder","sameBytes","a","b","length","i","encoder","TextEncoder","decodeXmp","bytes","text","decode","end","charCodeAt","packet","slice","trim","readTiffFile","xmp","iim","irb","irbCopy","iimWrite","current","write","ifd","tag","type","UNDEFINED","value","kept","BYTE","LONG","includes","padded","Uint8Array","Math","ceil","set","writeTiffFile","update","writes","currentXmp","sameXmp","undefined","push","encode","sameIim","irbHoldsIim","patch","exifPatch","allowAdd"],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
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 { MediaEmbeddedPatch, MediaEmbeddedSource } from '../media-embedded-fields';
|
|
18
|
+
/**
|
|
19
|
+
* One value read from one metadata block, before the blocks are reconciled
|
|
20
|
+
* (AGL-3331). A photo's caption arrives as up to three candidates — XMP,
|
|
21
|
+
* IPTC, EXIF — and `index.ts` folds them into one field.
|
|
22
|
+
*/
|
|
23
|
+
export interface EmbeddedCandidate {
|
|
24
|
+
/**
|
|
25
|
+
* A canonical catalog key (`title`, `creator`, …) or a namespaced other
|
|
26
|
+
* key from `otherEmbeddedKey` — see `media-embedded-fields.ts` for both
|
|
27
|
+
* and for the value format each canonical key uses.
|
|
28
|
+
*/
|
|
29
|
+
key: string;
|
|
30
|
+
value: string | string[];
|
|
31
|
+
source: MediaEmbeddedSource;
|
|
32
|
+
/** Display label for an other key (`photoshop:TransmissionReference`). */
|
|
33
|
+
label?: string;
|
|
34
|
+
/**
|
|
35
|
+
* False when THIS value cannot be written back although its key can on
|
|
36
|
+
* this format — a structured XMP property, a PDF entry that is not a text
|
|
37
|
+
* string. The field is shown and not offered for editing.
|
|
38
|
+
*/
|
|
39
|
+
editable?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** An edit, keyed as the reader keyed it. Re-exported for the modules. */
|
|
42
|
+
export type EmbeddedPatch = MediaEmbeddedPatch;
|
|
43
|
+
/**
|
|
44
|
+
* Random access to a stored object, so a reader of a 200 MB film can fetch
|
|
45
|
+
* its `moov` box without downloading the film. `end` is EXCLUSIVE and is
|
|
46
|
+
* clamped by the implementation to `size`.
|
|
47
|
+
*/
|
|
48
|
+
export interface EmbeddedByteReader {
|
|
49
|
+
size: number;
|
|
50
|
+
read(start: number, end: number): Promise<Uint8Array>;
|
|
51
|
+
}
|
|
52
|
+
/** Wraps bytes already in memory as a reader. */
|
|
53
|
+
export declare function bytesReader(bytes: Uint8Array): EmbeddedByteReader;
|
|
54
|
+
/**
|
|
55
|
+
* A write the format cannot take, with a sentence for the person who asked.
|
|
56
|
+
* Thrown by writers; the route answers it as a 422 rather than a 500.
|
|
57
|
+
*/
|
|
58
|
+
export declare class EmbeddedWriteError extends Error {
|
|
59
|
+
constructor(message: string);
|
|
60
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
*/ /** Wraps bytes already in memory as a reader. */ export function bytesReader(bytes) {
|
|
17
|
+
return {
|
|
18
|
+
size: bytes.length,
|
|
19
|
+
read: async (start, end)=>bytes.subarray(Math.max(0, start), Math.min(bytes.length, end))
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A write the format cannot take, with a sentence for the person who asked.
|
|
24
|
+
* Thrown by writers; the route answers it as a 422 rather than a 500.
|
|
25
|
+
*/ export class EmbeddedWriteError extends Error {
|
|
26
|
+
constructor(message){
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'EmbeddedWriteError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/aglyn/src/lib/app-utils/media-embedded-metadata/types.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\nimport type {\n MediaEmbeddedPatch,\n MediaEmbeddedSource,\n} from '../media-embedded-fields'\n\n/**\n * One value read from one metadata block, before the blocks are reconciled\n * (AGL-3331). A photo's caption arrives as up to three candidates — XMP,\n * IPTC, EXIF — and `index.ts` folds them into one field.\n */\nexport interface EmbeddedCandidate {\n /**\n * A canonical catalog key (`title`, `creator`, …) or a namespaced other\n * key from `otherEmbeddedKey` — see `media-embedded-fields.ts` for both\n * and for the value format each canonical key uses.\n */\n key: string\n value: string | string[]\n source: MediaEmbeddedSource\n /** Display label for an other key (`photoshop:TransmissionReference`). */\n label?: string\n /**\n * False when THIS value cannot be written back although its key can on\n * this format — a structured XMP property, a PDF entry that is not a text\n * string. The field is shown and not offered for editing.\n */\n editable?: boolean\n}\n\n/** An edit, keyed as the reader keyed it. Re-exported for the modules. */\nexport type EmbeddedPatch = MediaEmbeddedPatch\n\n/**\n * Random access to a stored object, so a reader of a 200 MB film can fetch\n * its `moov` box without downloading the film. `end` is EXCLUSIVE and is\n * clamped by the implementation to `size`.\n */\nexport interface EmbeddedByteReader {\n size: number\n read(start: number, end: number): Promise<Uint8Array>\n}\n\n/** Wraps bytes already in memory as a reader. */\nexport function bytesReader(bytes: Uint8Array): EmbeddedByteReader {\n return {\n size: bytes.length,\n read: async (start, end) =>\n bytes.subarray(Math.max(0, start), Math.min(bytes.length, end)),\n }\n}\n\n/**\n * A write the format cannot take, with a sentence for the person who asked.\n * Thrown by writers; the route answers it as a 422 rather than a 500.\n */\nexport class EmbeddedWriteError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'EmbeddedWriteError'\n }\n}\n"],"names":["bytesReader","bytes","size","length","read","start","end","subarray","Math","max","min","EmbeddedWriteError","Error","message","name"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GA4CD,+CAA+C,GAC/C,OAAO,SAASA,YAAYC,KAAiB;IAC3C,OAAO;QACLC,MAAMD,MAAME,MAAM;QAClBC,MAAM,OAAOC,OAAOC,MAClBL,MAAMM,QAAQ,CAACC,KAAKC,GAAG,CAAC,GAAGJ,QAAQG,KAAKE,GAAG,CAACT,MAAME,MAAM,EAAEG;IAC9D;AACF;AAEA;;;CAGC,GACD,OAAO,MAAMK,2BAA2BC;IACtC,YAAYC,OAAe,CAAE;QAC3B,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
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 { EmbeddedByteReader, EmbeddedCandidate } from './types';
|
|
18
|
+
/**
|
|
19
|
+
* Read a film's tags through random access.
|
|
20
|
+
*
|
|
21
|
+
* Null when the bytes do not open with an EBML header whose DocType is
|
|
22
|
+
* `webm` or `matroska`. Every candidate is `editable: false`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readWebm(reader: EmbeddedByteReader): Promise<{
|
|
25
|
+
candidates: EmbeddedCandidate[];
|
|
26
|
+
} | null>;
|