safe-upload 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,23 @@
1
+ import type { Category } from './types.js';
2
+ /** 'technical' tags are needed to display the image correctly and are never removed. */
3
+ export type TagClass = Category | 'technical';
4
+ export type IfdName = 'ifd0' | 'exif' | 'gps' | 'interop' | 'ifd1';
5
+ interface TagDef {
6
+ name: string;
7
+ label: string;
8
+ cls: TagClass;
9
+ }
10
+ /** IFD0 (also used for IFD1 lookups of shared tags). */
11
+ export declare const IFD0_TAGS: Record<number, TagDef>;
12
+ export declare const EXIF_TAGS: Record<number, TagDef>;
13
+ export declare const GPS_TAGS: Record<number, TagDef>;
14
+ export declare const INTEROP_TAGS: Record<number, TagDef>;
15
+ export declare const POINTER_TAGS: {
16
+ readonly exif: 34665;
17
+ readonly gps: 34853;
18
+ readonly interop: 40965;
19
+ };
20
+ export declare function tagDef(ifd: IfdName, tag: number): TagDef | undefined;
21
+ /** Classify a tag. Everything in IFD1 belongs to the embedded thumbnail. */
22
+ export declare function classifyTag(ifd: IfdName, tag: number): TagClass;
23
+ export {};
@@ -0,0 +1,200 @@
1
+ const t = (name, label, cls) => ({ name, label, cls });
2
+ /** IFD0 (also used for IFD1 lookups of shared tags). */
3
+ export const IFD0_TAGS = {
4
+ 0x0100: t('ImageWidth', 'Image width', 'technical'),
5
+ 0x0101: t('ImageHeight', 'Image height', 'technical'),
6
+ 0x0102: t('BitsPerSample', 'Bits per sample', 'technical'),
7
+ 0x0103: t('Compression', 'Compression', 'technical'),
8
+ 0x0106: t('PhotometricInterpretation', 'Photometric interpretation', 'technical'),
9
+ 0x010e: t('ImageDescription', 'Image description', 'descriptions'),
10
+ 0x010f: t('Make', 'Camera make', 'device'),
11
+ 0x0110: t('Model', 'Camera model', 'device'),
12
+ 0x0112: t('Orientation', 'Orientation', 'technical'),
13
+ 0x0115: t('SamplesPerPixel', 'Samples per pixel', 'technical'),
14
+ 0x011a: t('XResolution', 'X resolution', 'technical'),
15
+ 0x011b: t('YResolution', 'Y resolution', 'technical'),
16
+ 0x011c: t('PlanarConfiguration', 'Planar configuration', 'technical'),
17
+ 0x0128: t('ResolutionUnit', 'Resolution unit', 'technical'),
18
+ 0x012d: t('TransferFunction', 'Transfer function', 'technical'),
19
+ 0x0131: t('Software', 'Software', 'software'),
20
+ 0x0132: t('DateTime', 'Modified', 'time'),
21
+ 0x013b: t('Artist', 'Artist', 'author'),
22
+ 0x013c: t('HostComputer', 'Computer name', 'device'),
23
+ 0x013e: t('WhitePoint', 'White point', 'technical'),
24
+ 0x013f: t('PrimaryChromaticities', 'Primary chromaticities', 'technical'),
25
+ 0x0201: t('JPEGInterchangeFormat', 'Thumbnail offset', 'thumbnail'),
26
+ 0x0202: t('JPEGInterchangeFormatLength', 'Thumbnail length', 'thumbnail'),
27
+ 0x0211: t('YCbCrCoefficients', 'YCbCr coefficients', 'technical'),
28
+ 0x0212: t('YCbCrSubSampling', 'YCbCr subsampling', 'technical'),
29
+ 0x0213: t('YCbCrPositioning', 'YCbCr positioning', 'technical'),
30
+ 0x0214: t('ReferenceBlackWhite', 'Reference black/white', 'technical'),
31
+ 0x02bc: t('ApplicationNotes', 'Embedded XMP', 'history'),
32
+ 0x4746: t('Rating', 'Rating', 'descriptions'),
33
+ 0x4749: t('RatingPercent', 'Rating percent', 'descriptions'),
34
+ 0x8298: t('Copyright', 'Copyright', 'author'),
35
+ 0x83bb: t('IPTC-NAA', 'Embedded IPTC', 'descriptions'),
36
+ 0x8649: t('PhotoshopSettings', 'Photoshop data', 'history'),
37
+ 0x8773: t('ICCProfile', 'Colour profile', 'technical'),
38
+ 0x9c9b: t('XPTitle', 'Title', 'descriptions'),
39
+ 0x9c9c: t('XPComment', 'Comment', 'descriptions'),
40
+ 0x9c9d: t('XPAuthor', 'Author', 'author'),
41
+ 0x9c9e: t('XPKeywords', 'Keywords', 'descriptions'),
42
+ 0x9c9f: t('XPSubject', 'Subject', 'descriptions'),
43
+ 0xa480: t('GDALMetadata', 'GDAL metadata', 'other'),
44
+ 0xc4a5: t('PrintIM', 'Print settings', 'software'),
45
+ 0xc614: t('UniqueCameraModel', 'Unique camera model', 'device'),
46
+ 0xc62f: t('CameraSerialNumber', 'Camera serial number', 'device'),
47
+ 0xea1c: t('Padding', 'Padding', 'technical'),
48
+ 0xea1d: t('OffsetSchema', 'Offset schema', 'technical'),
49
+ };
50
+ export const EXIF_TAGS = {
51
+ 0x829a: t('ExposureTime', 'Exposure time', 'settings'),
52
+ 0x829d: t('FNumber', 'Aperture', 'settings'),
53
+ 0x8822: t('ExposureProgram', 'Exposure program', 'settings'),
54
+ 0x8824: t('SpectralSensitivity', 'Spectral sensitivity', 'settings'),
55
+ 0x8827: t('ISO', 'ISO', 'settings'),
56
+ 0x8828: t('OECF', 'OECF', 'settings'),
57
+ 0x8830: t('SensitivityType', 'Sensitivity type', 'settings'),
58
+ 0x8831: t('StandardOutputSensitivity', 'Standard output sensitivity', 'settings'),
59
+ 0x8832: t('RecommendedExposureIndex', 'Recommended exposure index', 'settings'),
60
+ 0x9000: t('ExifVersion', 'EXIF version', 'technical'),
61
+ 0x9003: t('DateTimeOriginal', 'Taken', 'time'),
62
+ 0x9004: t('DateTimeDigitized', 'Digitised', 'time'),
63
+ 0x9010: t('OffsetTime', 'Time zone (modified)', 'time'),
64
+ 0x9011: t('OffsetTimeOriginal', 'Time zone', 'time'),
65
+ 0x9012: t('OffsetTimeDigitized', 'Time zone (digitised)', 'time'),
66
+ 0x9101: t('ComponentsConfiguration', 'Components configuration', 'technical'),
67
+ 0x9102: t('CompressedBitsPerPixel', 'Compressed bits per pixel', 'technical'),
68
+ 0x9201: t('ShutterSpeedValue', 'Shutter speed', 'settings'),
69
+ 0x9202: t('ApertureValue', 'Aperture value', 'settings'),
70
+ 0x9203: t('BrightnessValue', 'Brightness', 'settings'),
71
+ 0x9204: t('ExposureBiasValue', 'Exposure bias', 'settings'),
72
+ 0x9205: t('MaxApertureValue', 'Max aperture', 'settings'),
73
+ 0x9206: t('SubjectDistance', 'Subject distance', 'settings'),
74
+ 0x9207: t('MeteringMode', 'Metering mode', 'settings'),
75
+ 0x9208: t('LightSource', 'Light source', 'settings'),
76
+ 0x9209: t('Flash', 'Flash', 'settings'),
77
+ 0x920a: t('FocalLength', 'Focal length', 'settings'),
78
+ 0x9214: t('SubjectArea', 'Subject area', 'settings'),
79
+ 0x927c: t('MakerNote', 'Maker note (vendor data, often includes serial numbers)', 'device'),
80
+ 0x9286: t('UserComment', 'User comment', 'descriptions'),
81
+ 0x9290: t('SubSecTime', 'Sub-second time', 'time'),
82
+ 0x9291: t('SubSecTimeOriginal', 'Sub-second time (taken)', 'time'),
83
+ 0x9292: t('SubSecTimeDigitized', 'Sub-second time (digitised)', 'time'),
84
+ 0x9400: t('AmbientTemperature', 'Ambient temperature', 'settings'),
85
+ 0x9401: t('Humidity', 'Humidity', 'settings'),
86
+ 0x9402: t('Pressure', 'Air pressure', 'settings'),
87
+ 0x9403: t('WaterDepth', 'Water depth', 'settings'),
88
+ 0x9404: t('Acceleration', 'Acceleration', 'settings'),
89
+ 0x9405: t('CameraElevationAngle', 'Camera elevation angle', 'settings'),
90
+ 0xa000: t('FlashpixVersion', 'Flashpix version', 'technical'),
91
+ 0xa001: t('ColorSpace', 'Colour space', 'technical'),
92
+ 0xa002: t('PixelXDimension', 'Pixel width', 'technical'),
93
+ 0xa003: t('PixelYDimension', 'Pixel height', 'technical'),
94
+ 0xa004: t('RelatedSoundFile', 'Related sound file', 'other'),
95
+ 0xa20b: t('FlashEnergy', 'Flash energy', 'settings'),
96
+ 0xa20e: t('FocalPlaneXResolution', 'Focal plane X resolution', 'settings'),
97
+ 0xa20f: t('FocalPlaneYResolution', 'Focal plane Y resolution', 'settings'),
98
+ 0xa210: t('FocalPlaneResolutionUnit', 'Focal plane resolution unit', 'settings'),
99
+ 0xa214: t('SubjectLocation', 'Subject location', 'settings'),
100
+ 0xa215: t('ExposureIndex', 'Exposure index', 'settings'),
101
+ 0xa217: t('SensingMethod', 'Sensing method', 'settings'),
102
+ 0xa300: t('FileSource', 'File source', 'settings'),
103
+ 0xa301: t('SceneType', 'Scene type', 'settings'),
104
+ 0xa302: t('CFAPattern', 'CFA pattern', 'settings'),
105
+ 0xa401: t('CustomRendered', 'Custom rendered', 'settings'),
106
+ 0xa402: t('ExposureMode', 'Exposure mode', 'settings'),
107
+ 0xa403: t('WhiteBalance', 'White balance', 'settings'),
108
+ 0xa404: t('DigitalZoomRatio', 'Digital zoom', 'settings'),
109
+ 0xa405: t('FocalLengthIn35mmFormat', 'Focal length (35 mm)', 'settings'),
110
+ 0xa406: t('SceneCaptureType', 'Scene capture type', 'settings'),
111
+ 0xa407: t('GainControl', 'Gain control', 'settings'),
112
+ 0xa408: t('Contrast', 'Contrast', 'settings'),
113
+ 0xa409: t('Saturation', 'Saturation', 'settings'),
114
+ 0xa40a: t('Sharpness', 'Sharpness', 'settings'),
115
+ 0xa40b: t('DeviceSettingDescription', 'Device setting description', 'settings'),
116
+ 0xa40c: t('SubjectDistanceRange', 'Subject distance range', 'settings'),
117
+ 0xa420: t('ImageUniqueID', 'Image unique ID', 'device'),
118
+ 0xa430: t('CameraOwnerName', 'Camera owner name', 'author'),
119
+ 0xa431: t('BodySerialNumber', 'Camera serial number', 'device'),
120
+ 0xa432: t('LensSpecification', 'Lens specification', 'device'),
121
+ 0xa433: t('LensMake', 'Lens make', 'device'),
122
+ 0xa434: t('LensModel', 'Lens model', 'device'),
123
+ 0xa435: t('LensSerialNumber', 'Lens serial number', 'device'),
124
+ 0xa436: t('ImageTitle', 'Image title', 'descriptions'),
125
+ 0xa437: t('Photographer', 'Photographer', 'author'),
126
+ 0xa438: t('ImageEditor', 'Image editor', 'author'),
127
+ 0xa439: t('CameraFirmware', 'Camera firmware', 'device'),
128
+ 0xa43a: t('RAWDevelopingSoftware', 'RAW developing software', 'software'),
129
+ 0xa43b: t('ImageEditingSoftware', 'Image editing software', 'software'),
130
+ 0xa43c: t('MetadataEditingSoftware', 'Metadata editing software', 'software'),
131
+ 0xa460: t('CompositeImage', 'Composite image', 'settings'),
132
+ 0xa461: t('CompositeImageCount', 'Composite image count', 'settings'),
133
+ 0xa462: t('CompositeImageExposureTimes', 'Composite exposure times', 'settings'),
134
+ 0xa500: t('Gamma', 'Gamma', 'technical'),
135
+ };
136
+ export const GPS_TAGS = {
137
+ 0x0000: t('GPSVersionID', 'GPS version', 'location'),
138
+ 0x0001: t('GPSLatitudeRef', 'GPS latitude reference', 'location'),
139
+ 0x0002: t('GPSLatitude', 'GPS latitude', 'location'),
140
+ 0x0003: t('GPSLongitudeRef', 'GPS longitude reference', 'location'),
141
+ 0x0004: t('GPSLongitude', 'GPS longitude', 'location'),
142
+ 0x0005: t('GPSAltitudeRef', 'GPS altitude reference', 'location'),
143
+ 0x0006: t('GPSAltitude', 'GPS altitude', 'location'),
144
+ 0x0007: t('GPSTimeStamp', 'GPS time (UTC)', 'time'),
145
+ 0x0008: t('GPSSatellites', 'GPS satellites', 'location'),
146
+ 0x0009: t('GPSStatus', 'GPS status', 'location'),
147
+ 0x000a: t('GPSMeasureMode', 'GPS measure mode', 'location'),
148
+ 0x000b: t('GPSDOP', 'GPS precision', 'location'),
149
+ 0x000c: t('GPSSpeedRef', 'GPS speed unit', 'location'),
150
+ 0x000d: t('GPSSpeed', 'Speed', 'location'),
151
+ 0x000e: t('GPSTrackRef', 'GPS track reference', 'location'),
152
+ 0x000f: t('GPSTrack', 'Direction of travel', 'location'),
153
+ 0x0010: t('GPSImgDirectionRef', 'Camera direction reference', 'location'),
154
+ 0x0011: t('GPSImgDirection', 'Camera facing', 'location'),
155
+ 0x0012: t('GPSMapDatum', 'GPS map datum', 'location'),
156
+ 0x0013: t('GPSDestLatitudeRef', 'Destination latitude reference', 'location'),
157
+ 0x0014: t('GPSDestLatitude', 'Destination latitude', 'location'),
158
+ 0x0015: t('GPSDestLongitudeRef', 'Destination longitude reference', 'location'),
159
+ 0x0016: t('GPSDestLongitude', 'Destination longitude', 'location'),
160
+ 0x0017: t('GPSDestBearingRef', 'Destination bearing reference', 'location'),
161
+ 0x0018: t('GPSDestBearing', 'Destination bearing', 'location'),
162
+ 0x0019: t('GPSDestDistanceRef', 'Destination distance unit', 'location'),
163
+ 0x001a: t('GPSDestDistance', 'Destination distance', 'location'),
164
+ 0x001b: t('GPSProcessingMethod', 'GPS processing method', 'location'),
165
+ 0x001c: t('GPSAreaInformation', 'GPS area information', 'location'),
166
+ 0x001d: t('GPSDateStamp', 'GPS date (UTC)', 'time'),
167
+ 0x001e: t('GPSDifferential', 'GPS differential', 'location'),
168
+ 0x001f: t('GPSHPositioningError', 'GPS horizontal error', 'location'),
169
+ };
170
+ export const INTEROP_TAGS = {
171
+ 0x0001: t('InteropIndex', 'Interoperability index', 'technical'),
172
+ 0x0002: t('InteropVersion', 'Interoperability version', 'technical'),
173
+ 0x1000: t('RelatedImageFileFormat', 'Related image format', 'technical'),
174
+ 0x1001: t('RelatedImageWidth', 'Related image width', 'technical'),
175
+ 0x1002: t('RelatedImageHeight', 'Related image height', 'technical'),
176
+ };
177
+ export const POINTER_TAGS = { exif: 0x8769, gps: 0x8825, interop: 0xa005 };
178
+ export function tagDef(ifd, tag) {
179
+ switch (ifd) {
180
+ case 'ifd0':
181
+ case 'ifd1':
182
+ return IFD0_TAGS[tag];
183
+ case 'exif':
184
+ return EXIF_TAGS[tag];
185
+ case 'gps':
186
+ return GPS_TAGS[tag];
187
+ case 'interop':
188
+ return INTEROP_TAGS[tag];
189
+ }
190
+ }
191
+ /** Classify a tag. Everything in IFD1 belongs to the embedded thumbnail. */
192
+ export function classifyTag(ifd, tag) {
193
+ if (ifd === 'ifd1')
194
+ return 'thumbnail';
195
+ if (ifd === 'gps')
196
+ return GPS_TAGS[tag]?.cls ?? 'location';
197
+ if (ifd === 'interop')
198
+ return 'technical';
199
+ return tagDef(ifd, tag)?.cls ?? 'other';
200
+ }
package/dist/exif.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { type IfdName } from './exif-tags.js';
2
+ import type { Category, Finding, GpsPoint, Source } from './types.js';
3
+ export interface IfdEntry {
4
+ tag: number;
5
+ type: number;
6
+ count: number;
7
+ /** Raw value bytes in the file's byte order (length = typeSize * count). */
8
+ value: Uint8Array;
9
+ }
10
+ export interface Tiff {
11
+ le: boolean;
12
+ ifds: Partial<Record<IfdName, IfdEntry[]>>;
13
+ /** JPEG thumbnail referenced from IFD1, if any. */
14
+ thumbnail?: Uint8Array;
15
+ /** IFD1 stores an uncompressed (strip) thumbnail we cannot relocate. */
16
+ stripThumbnail: boolean;
17
+ warnings: string[];
18
+ }
19
+ export declare function parseTiff(b: Uint8Array): Tiff | null;
20
+ export declare function entryNumbers(e: IfdEntry, le: boolean): number[];
21
+ export declare function entryText(e: IfdEntry): string;
22
+ /** Human-readable value of an entry. */
23
+ export declare function formatEntry(ifd: IfdName, e: IfdEntry, le: boolean): string;
24
+ /** True when the GPS block carries an actual position, direction, destination or area. */
25
+ export declare function gpsInformative(tiff: Tiff): boolean;
26
+ export interface ExifAnalysis {
27
+ findings: Finding[];
28
+ gps?: GpsPoint;
29
+ capturedAt?: string;
30
+ timeZone?: string;
31
+ device?: string;
32
+ serialNumbers: string[];
33
+ names: string[];
34
+ software: string[];
35
+ orientation?: number;
36
+ width?: number;
37
+ height?: number;
38
+ thumbnail?: Uint8Array;
39
+ }
40
+ export declare function analyzeTiff(tiff: Tiff, source: Source, removable?: boolean): ExifAnalysis;
41
+ export declare function tiffCategories(tiff: Tiff): Set<Category>;
42
+ export interface BuildOptions {
43
+ remove: Set<Category>;
44
+ /** Override orientation (used after re-encoding already-rotated pixels). */
45
+ orientation?: number;
46
+ /** Override pixel dimensions (used after re-encoding). */
47
+ dimensions?: {
48
+ width: number;
49
+ height: number;
50
+ };
51
+ }
52
+ export interface BuildResult {
53
+ bytes: Uint8Array | null;
54
+ removed: Set<Category>;
55
+ notes: string[];
56
+ }
57
+ /**
58
+ * Rebuild a TIFF/EXIF block without the removed categories. Values are copied byte-for-byte in the
59
+ * original byte order. The vendor MakerNote is always dropped on rewrite because its internal
60
+ * offsets break when relocated. Returns bytes=null when nothing is left worth keeping.
61
+ */
62
+ export declare function buildTiff(tiff: Tiff, opts: BuildOptions): BuildResult;
63
+ /** "Exif\0\0" prefix used by JPEG APP1, WebP and HEIF Exif payloads. */
64
+ export declare const EXIF_HEADER: Uint8Array<ArrayBuffer>;