@kitschpatrol/aphex 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.
Binary file
@@ -0,0 +1,236 @@
1
+ -- Exports images from Photos via automated GUI manipulation
2
+ -- It's an unsavory approach, but no other means of automation seems to yield higher quality edited image exports (so far)
3
+ -- Arguments are passed in their order of appearance in the GUI, and expect values exactly matching the UI strings in Photos.app
4
+ -- Accepts an individual photo UUID, or an album UUID (use aphex-swift to look up identifiers / UUIDs)
5
+ on run { uuid, exportDirectory, photoKind, jpegQuality, tiffBitDepth, colorProfile, photoSize, maxSizeType, maxSizeValue, includeMetadata, includeLocation , fileName, sequentialPrefix, subfolderFormat}
6
+
7
+ tell application "System Events"
8
+ set wasRunning to (name of processes) contains "Photos"
9
+ end tell
10
+
11
+ tell application "Photos"
12
+ activate
13
+
14
+ -- Reset selection and view
15
+ tell application "System Events"
16
+ tell process "Photos"
17
+ repeat until exists menu bar 1
18
+ delay 0.25
19
+ end repeat
20
+ click menu item "Library" of menu 1 of menu item "Photos" of menu 1 of menu bar item "View" of menu bar 1
21
+ delay 0.1
22
+ click menu item "Deselect All" of menu 1 of menu bar item "Edit" of menu bar 1
23
+ end tell
24
+ end tell
25
+
26
+ -- Select photo or album for export by UUID
27
+ try
28
+ spotlight media item id (uuid)
29
+ on error
30
+ try
31
+ spotlight album id (uuid)
32
+ end try
33
+ end try
34
+
35
+ if (count of selection) is 0 then
36
+ log "No photos to export"
37
+ if not wasRunning then
38
+ tell application "Photos" to quit
39
+ end if
40
+ return
41
+ end if
42
+ end tell
43
+
44
+ -- Create export destination directory if necessary
45
+ -- try
46
+ -- do shell script "mkdir -p " & quoted form of exportDirectory
47
+ -- on error errMsg number errNum
48
+ -- display alert "Error creating directory" message errMsg & " (Error " & errNum & ")"
49
+ -- end try
50
+
51
+ -- Store initial state for logging
52
+ set initialFiles to filesInExportDirectory(exportDirectory)
53
+
54
+ tell application "System Events"
55
+ tell process "Photos"
56
+
57
+ -- Export command
58
+ repeat until exists menu bar 1
59
+ delay 0.1
60
+ end repeat
61
+ click menu item 1 of menu "Export" of menu item "Export" of menu "File" of menu bar item "File" of menu bar 1
62
+
63
+ repeat until exists sheet 1 of window 1
64
+ delay 0.1
65
+ end repeat
66
+
67
+ -- Set export preferences
68
+ tell sheet 1 of window 1
69
+ tell group "Photos"
70
+
71
+ -- Ensure the disclosure is open
72
+ if not (exists pop up button "Color Profile:") then
73
+ click UI element 3
74
+ delay 0.5
75
+ end if
76
+
77
+ tell pop up button "Photo Kind:"
78
+ if value is not photoKind then
79
+ click
80
+ click menu item photoKind of menu 1
81
+ delay 0.5
82
+ end if
83
+ end tell
84
+
85
+ if photoKind is "JPEG" then
86
+ tell pop up button "JPEG Quality:"
87
+ if value is not jpegQuality then
88
+ click
89
+ click menu item jpegQuality of menu 1
90
+ delay 0.5
91
+ end if
92
+ end tell
93
+ end if
94
+
95
+ if photoKind is "TIFF" then
96
+ tell checkbox "16 Bit"
97
+ if (value as boolean) is not (tiffBitDepth is 16) then
98
+ click
99
+ end if
100
+ end tell
101
+ end if
102
+
103
+ tell pop up button "Color Profile:"
104
+ if value is not colorProfile then
105
+ click
106
+ click menu item colorProfile of menu 1
107
+ delay 0.5
108
+ end if
109
+ end tell
110
+
111
+ tell pop up button "Size:"
112
+ if value is not photoSize then
113
+ click
114
+ click menu item photoSize of menu 1
115
+ delay 0.5
116
+ end if
117
+ end tell
118
+
119
+ if photoSize is "Custom" then
120
+ tell group 1
121
+
122
+ tell pop up button "Max"
123
+ if value is not maxSizeType then
124
+ click
125
+ click menu item maxSizeType of menu 1
126
+ delay 0.5
127
+ end if
128
+ end tell
129
+
130
+ tell text field 1
131
+ if value is not maxSizeValue then
132
+ set value of attribute "AXFocused" to true
133
+ set value of attribute "AXValue" to maxSizeValue as string
134
+ perform action "AXConfirm"
135
+ end if
136
+ end tell
137
+
138
+ end tell
139
+ end if
140
+
141
+ end tell
142
+
143
+ tell group "Info"
144
+ tell checkbox "Title, Keywords, and Caption"
145
+ if (value as boolean) is not (includeMetadata as boolean) then
146
+ click
147
+ end if
148
+ end tell
149
+ tell checkbox "Location Information"
150
+ if (value as boolean) is not (includeLocation as boolean) then
151
+ click
152
+ end if
153
+ end tell
154
+ end tell
155
+
156
+ tell group "File Naming"
157
+ tell pop up button "File Name:"
158
+ if value is not fileName then
159
+ click
160
+ click menu item fileName of menu 1
161
+ delay 0.5
162
+ end if
163
+ end tell
164
+ if fileName is "Sequential" then
165
+ -- why not "Sequential Prefix:"?
166
+ tell text field 1
167
+ if value is not sequentialPrefix then
168
+ set value of attribute "AXFocused" to true
169
+ set value of attribute "AXValue" to sequentialPrefix
170
+ perform action "AXConfirm"
171
+ end if
172
+ end tell
173
+ end if
174
+ tell pop up button "Subfolder Format:"
175
+ if value is not subfolderFormat then
176
+ click
177
+ click menu item subfolderFormat of menu 1
178
+ delay 0.5
179
+ end if
180
+ end tell
181
+ end tell
182
+
183
+ delay 0.5
184
+ click button "Export"
185
+
186
+ end tell
187
+
188
+ -- Set export location
189
+ repeat until exists sheet 1 of window 1
190
+ delay 0.1
191
+ end repeat
192
+
193
+ tell sheet 1 of window 1
194
+ keystroke "g" using {command down, shift down}
195
+ repeat until exists sheet 1
196
+ delay 0.1
197
+ end repeat
198
+ tell sheet 1
199
+ keystroke exportDirectory
200
+ key code 76
201
+ end tell
202
+
203
+ -- Getting stuck here...
204
+ delay 1
205
+ click button "Export"
206
+ end tell
207
+
208
+ -- Wait for the export to finish, but allow a few second for the progress indicator to appear
209
+ set startTime to (current date)
210
+ repeat until (not (exists progress indicator 1 of group 2 of toolbar 1 of window 1)) and ((current date) - startTime) > 2
211
+ delay 0.25
212
+ end repeat
213
+
214
+ end tell
215
+ end tell
216
+
217
+ -- Clean up
218
+ if not wasRunning then
219
+ tell application "Photos" to quit
220
+ end if
221
+
222
+ -- Log the exported files
223
+ set currentFiles to filesInExportDirectory(exportDirectory)
224
+ repeat with aFile in currentFiles
225
+ if (initialFiles does not contain aFile) then
226
+ set end of initialFiles to aFile
227
+ log aFile
228
+ end if
229
+ end repeat
230
+
231
+ end run
232
+
233
+ -- Handler to get list of files excluding hidden ones
234
+ on filesInExportDirectory(exportDirectory)
235
+ return paragraphs of (do shell script "find " & quoted form of exportDirectory & " -type f \\! -name \".*\"")
236
+ end filesInExportDirectory
@@ -0,0 +1,373 @@
1
+ import { ExifTool } from "exiftool-vendored";
2
+ import { ILogBasic, ILogLayer } from "lognow";
3
+ import { OmitDeep, PartialDeep, Simplify } from "type-fest";
4
+
5
+ //#region src/aphex-swift/cli-bridge.d.ts
6
+ /**
7
+ * Start an interactive session with aphex-swift.
8
+ * Commands will be sent to this persistent process instead of spawning new ones.
9
+ * @throws {Error} If the session is already active or fails to start
10
+ */
11
+ declare function interactiveSessionStart(): Promise<void>;
12
+ /**
13
+ * Stop the interactive session if one is active.
14
+ */
15
+ declare function interactiveSessionStop(): Promise<void>;
16
+ /**
17
+ * TypeScript type definition for ResourceInfo from the Swift implementation
18
+ */
19
+ type ResourceInfo = {
20
+ contentType: string;
21
+ fileName: string;
22
+ filePath: string;
23
+ fileSize: number;
24
+ height: number;
25
+ width: number;
26
+ };
27
+ /**
28
+ * TypeScript type definition for the JSON representation of a PHAsset
29
+ * from the iOS Photos framework (CodablePHAsset)
30
+ */
31
+ type PhotoInfo = {
32
+ dateCreated: Date;
33
+ dateModified: Date;
34
+ edited?: ResourceInfo;
35
+ favorite: boolean;
36
+ hidden: boolean;
37
+ original: ResourceInfo;
38
+ title?: string;
39
+ uuid: string;
40
+ };
41
+ type AlbumInfo = {
42
+ dateEnd?: Date;
43
+ dateStart?: Date;
44
+ estimatedAssetCount: number;
45
+ path: string;
46
+ subtype: number;
47
+ title: string;
48
+ type: number;
49
+ uuid: string;
50
+ };
51
+ /**
52
+ * Get photo asset information for given identifiers (ID, filename, album name, or photo path)
53
+ * @throws {Error} If the command fails
54
+ */
55
+ declare function aphexPhotoInfo(identifiers: string | string[], caseSensitive?: boolean): Promise<PhotoInfo[]>;
56
+ /**
57
+ * Get album info
58
+ * @throws {Error} If the command fails
59
+ */
60
+ declare function aphexAlbumInfo(identifiers: string | string[], caseSensitive?: boolean): Promise<AlbumInfo[]>;
61
+ //#endregion
62
+ //#region src/utilities/image/mime.d.ts
63
+ declare const IMAGE_MIME_TYPES: readonly ["arw", "avif", "bmp", "cr2", "cr3", "crw", "tga", "dng", "gif", "heic", "heif", "jpeg", "nef", "pef", "png", "psd", "svg+xml", "tiff", "webp"];
64
+ type ImageMimeType = (typeof IMAGE_MIME_TYPES)[number];
65
+ //#endregion
66
+ //#region src/pipeline/engines/applescript-gui.d.ts
67
+ /**
68
+ * Mirrors the export UI in Note that note that some options are contingent on others.
69
+ *
70
+ * The AppleScript implementation is smart about only "clicking" the necessary
71
+ * options in the GUI.
72
+ */
73
+ type ExportViaAppleScriptGuiOptions = {
74
+ /** "Color Profile" Drop Down */colorProfile: 'AdobeRGB' | 'Display P3' | 'Most Compatible' | 'Original' | 'sRGB'; /** "File Name" Drop Down */
75
+ fileName: 'Album Name With Number' | 'Sequential' | 'Use File Name' | 'Use Title'; /** "Include Location Information" Checkbox */
76
+ includeLocation: boolean; /** "Include Title, Keywords, and Caption" Checkbox */
77
+ includeMetadata: boolean;
78
+ /**
79
+ * "JPEG Quality" Drop Down
80
+ *
81
+ * Only applies if `photoKind` is `JPEG`
82
+ */
83
+ jpegQuality: 'High' | 'Low (smallest Size)' | 'Maximum' | 'Medium';
84
+ /**
85
+ * "Size Max" Drop Down
86
+ *
87
+ * Only applies if `photoSize` is `Custom`
88
+ */
89
+ maxSizeType: 'Dimension' | 'Height' | 'Width';
90
+ /**
91
+ * "Size Max of" text field
92
+ *
93
+ * Only applies if `photoSize` is `Custom`
94
+ */
95
+ maxSizeValue: number; /** "Photo Kind" Drop Down */
96
+ photoKind: 'HEIC' | 'JPEG' | 'PNG' | 'TIFF'; /** "Size" Drop Down */
97
+ photoSize: 'Custom' | 'Full Size' | 'Large' | 'Medium' | 'Small';
98
+ /**
99
+ * "Sequential Prefix" Text Field
100
+ *
101
+ * Only applies if `fileName` is `Sequential`
102
+ */
103
+ sequentialPrefix: string; /** "Subfolder Format" Drop Down */
104
+ subfolderFormat: 'Moment Name' | 'None';
105
+ /**
106
+ * "16 Bit" Checkbox
107
+ *
108
+ * Only applies if `photoKind` is `TIFF`
109
+ */
110
+ tiffBitDepth: 8 | 16;
111
+ };
112
+ //#endregion
113
+ //#region src/pipeline/image-export.d.ts
114
+ type ExportEngine = 'applescript' | 'file-system' | 'photos-gui' | 'swift-photokit';
115
+ type ExportEngineOptions = ExportEngine | Partial<Record<ImageMimeType, ExportEngine>>;
116
+ type ExportApplePhotoOptions = {
117
+ appleScriptGuiOptions: ExportViaAppleScriptGuiOptions;
118
+ engineEdited: ExportEngineOptions;
119
+ engineEditedAlpha: ExportEngineOptions;
120
+ engineOriginal: ExportEngineOptions;
121
+ engineOriginalAlpha: ExportEngineOptions;
122
+ /**
123
+ * Append a few digits from the image's local identifier in Photos.app, useful to avoid name collisions if exporting multiple album-worth of photos.
124
+ * Only applies to `FileNameOptions` `title` or `uuid`. This is NOT the same as a content hash.
125
+ */
126
+ fileNameAppendUuidFragment: boolean;
127
+ fileNameNormalizeExtensions: boolean;
128
+ fileNamePrecedence: FileNameOptions[];
129
+ fileNameSluggify: boolean;
130
+ };
131
+ type ExportApplePhotoResult = {
132
+ exportEngine: ExportEngine;
133
+ exportOptions: ExportApplePhotoOptions;
134
+ path: string;
135
+ photoInfo: PhotoInfo;
136
+ };
137
+ type FileNameOptions = 'fileName' | 'title' | 'uuid';
138
+ /** Also normalizes */
139
+ //#endregion
140
+ //#region src/utilities/image/tags.d.ts
141
+ declare const VALID_LABELS: readonly ["animation", "diagram", "illustration", "image", "photo", "render", "screenshot", "video"];
142
+ type Label = (typeof VALID_LABELS)[number];
143
+ type ValidateTagsResult = {
144
+ issues: string[];
145
+ valid: boolean;
146
+ };
147
+ type AphexMetadata = {
148
+ exportOptions: ExportOptions;
149
+ photoInfo: PhotoInfo;
150
+ };
151
+ type ImageTags = {
152
+ aphexMetadata?: AphexMetadata | undefined; /** Human Name */
153
+ creator?: string | undefined; /** Organization */
154
+ credit?: string | undefined; /** Description, used as alt text in HTML content */
155
+ description?: string | undefined;
156
+ /**
157
+ Image Type, e.g. 'animation', 'diagram', 'illustration', 'screenshot',
158
+ 'image', 'photo', 'render', 'video'
159
+ */
160
+ label?: Label | undefined;
161
+ preservedFileName?: string | undefined;
162
+ };
163
+ //#endregion
164
+ //#region src/pipeline/image-metadata.d.ts
165
+ type ManageMetadataOptions = {
166
+ syncToEdited: boolean;
167
+ syncToTarget: boolean;
168
+ tagsToSync: Array<keyof ImageTags>;
169
+ validate: boolean;
170
+ writeAphexMetadata: boolean;
171
+ };
172
+ type ManageMetadataResult = {
173
+ photoInfo: PhotoInfo;
174
+ tagsToEdited: Array<keyof ImageTags> | undefined;
175
+ tagsToTarget: Array<keyof ImageTags> | undefined;
176
+ validationResult: undefined | ValidateTagsResult;
177
+ };
178
+ //#endregion
179
+ //#region src/utilities/image/color.d.ts
180
+ declare const validColorProfiles: readonly ["Adobe RGB (1998)", "Apple Wide Color Sharing Profile", "Display P3", "None", "ProPhoto RGB", "sRGB IEC61966-2.1", "Unsupported"];
181
+ type ColorProfile = (typeof validColorProfiles)[number];
182
+ /**
183
+ * Normalize the color profile of an image
184
+ */
185
+ declare function normalizeColorProfile(imagePath: string, preserveColorProfiles?: ColorProfile[], defaultColorProfile?: ColorProfile): Promise<'assigned' | 'converted' | 'no-action'>;
186
+ //#endregion
187
+ //#region src/utilities/image/convert.d.ts
188
+ type LosslessFormat = 'avif' | 'none' | 'png' | 'webp';
189
+ type NearLosslessFormat = 'none' | 'webp';
190
+ type LossyFormat = 'avif' | 'jpeg' | 'none' | 'webp';
191
+ /**
192
+ * Configuration options for image compression.
193
+ *
194
+ * The compression process follows a three-tier approach:
195
+ * 1. First attempts lossless compression if enabled
196
+ * 2. Falls back to near-lossless compression if lossless exceeds size limit
197
+ * 3. Finally uses lossy compression if other methods fail to meet size requirements
198
+ *
199
+ * Set format to 'none' to disable that compression tier.
200
+ */
201
+ type CompressImageOptions = {
202
+ /**
203
+ * Force compression even if the original image is already within the size limit.
204
+ * When false, images smaller than maxFileSizeBytes are copied without modification.
205
+ */
206
+ forceCompression: boolean;
207
+ /**
208
+ * Format to use for lossless compression (first attempt).
209
+ * - 'avif': Best compression but slower encoding
210
+ * - 'png': Good compatibility, optimized with oxipng
211
+ * - 'webp': Good balance of compression and speed
212
+ * - 'none': Skip lossless compression
213
+ */
214
+ losslessFormat: LosslessFormat;
215
+ /**
216
+ * Format to use for lossy compression (final fallback).
217
+ * - 'avif': Best compression but very slow encoding
218
+ * - 'jpeg': Good compatibility, uses mozjpeg encoder
219
+ * - 'webp': Good balance of compression and speed
220
+ * - 'none': Skip lossy compression (may result in oversized files)
221
+ */
222
+ lossyFormat: LossyFormat;
223
+ /**
224
+ * Quality level for lossy compression (0-1 scale).
225
+ * - 0: Lowest quality, smallest file size
226
+ * - 1: Highest quality, largest file size
227
+ * - Recommended range: 0.85-0.96 for high-quality images
228
+ */
229
+ lossyQuality: number;
230
+ /**
231
+ * Maximum allowed file size in bytes.
232
+ * Images exceeding this size will be compressed using the configured formats.
233
+ * The compression process stops when this target is met or all options are exhausted.
234
+ */
235
+ maxFileSizeBytes: number;
236
+ /**
237
+ * Format to use for near-lossless compression (second attempt).
238
+ * - 'webp': Only format currently supporting near-lossless mode
239
+ * - 'none': Skip near-lossless compression
240
+ */
241
+ nearLosslessFormat: NearLosslessFormat;
242
+ };
243
+ /**
244
+ * Compress an image to a target size
245
+ */
246
+ declare function compressImage(sourceImagePath: string, destinationDirectory: string, options: CompressImageOptions): Promise<{
247
+ compression: 'lossless' | 'lossy' | 'near-lossless' | 'none';
248
+ path: string;
249
+ }>;
250
+ //#endregion
251
+ //#region src/utilities/image/image.d.ts
252
+ type ImageInfo = {
253
+ alpha: boolean;
254
+ colorProfile: string;
255
+ dimensionsPixels: {
256
+ width: number;
257
+ height: number;
258
+ };
259
+ mime: ImageMimeType;
260
+ path: string;
261
+ sizeBytes: number;
262
+ tags: ImageTags;
263
+ };
264
+ //#endregion
265
+ //#region src/pipeline/image-process.d.ts
266
+ type ProcessImageOptions = CompressImageOptions & {
267
+ /** Fallback color profile applied when source profile is not in preserve list */defaultColorProfile: ColorProfile; /** Calculate SSIM/PSNR/DSSIM metrics comparing original vs processed images */
268
+ logSimilarity: boolean; /** Lossless format used specifically for images with alpha channels */
269
+ losslessFormatAlpha: LosslessFormat; /** Lossy format used specifically for images with alpha channels */
270
+ lossyFormatAlpha: LossyFormat; /** Maximum pixel dimensions before triggering resize (width OR height) */
271
+ maxDimensionsPixels: {
272
+ width: number;
273
+ height: number;
274
+ }; /** Near-lossless format used specifically for images with alpha channels */
275
+ nearLosslessFormatAlpha: NearLosslessFormat; /** Image formats that bypass conversion and compression entirely */
276
+ passthroughFormats: ImageMimeType[]; /** Color profiles that are kept unchanged instead of being normalized */
277
+ preserveColorProfiles: ColorProfile[];
278
+ };
279
+ type ProcessImageResult = {
280
+ input: ImageInfo;
281
+ output: ImageInfo;
282
+ path: string;
283
+ report: {
284
+ color: Awaited<ReturnType<typeof normalizeColorProfile>>;
285
+ compression: Awaited<ReturnType<typeof compressImage>>['compression'];
286
+ date: Date;
287
+ durationMs: number;
288
+ similarity?: {
289
+ dssim: number;
290
+ psnr: number;
291
+ ssim: number;
292
+ };
293
+ };
294
+ };
295
+ /**
296
+ * Process an image
297
+ *
298
+ * Run in parallel through a worker for album processing
299
+ */
300
+ declare function processImage(sourceImagePath: string, destinationDirectory: string, options: ProcessImageOptions): Promise<ProcessImageResult>;
301
+ //#endregion
302
+ //#region src/pipeline/image-sync.d.ts
303
+ type DiffStrategy = 'exif-tags' | 'export-options' | 'file-name' | 'force-update' | 'metadata-options' | 'photo-info' | 'process-options';
304
+ type SyncOptions = {
305
+ deleteOthers: boolean;
306
+ deleteTarget: boolean;
307
+ diffStrategies: DiffStrategy[];
308
+ forceUpdate: boolean;
309
+ };
310
+ type SyncResult = {
311
+ plan: Array<{
312
+ diffedVia: DiffStrategy | undefined;
313
+ matchFilePath: string | undefined;
314
+ photoInfo: PhotoInfo;
315
+ status: 'changed' | 'new' | 'unchanged';
316
+ }>;
317
+ toDelete: string[];
318
+ };
319
+ //#endregion
320
+ //#region src/utilities/exiftool.d.ts
321
+ /**
322
+ * End the exiftool process gracefully
323
+ *
324
+ * This should be called when you're done using exiftool to prevent
325
+ * hanging processes. It's safe to call multiple times.
326
+ */
327
+ declare function endExiftool(): Promise<void>;
328
+ //#endregion
329
+ //#region src/utilities/log.d.ts
330
+ /**
331
+ * Set the logger instance for the module.
332
+ * Export this for library consumers to inject their own logger.
333
+ * @param logger - Accepts either a LogLayer instance or a Console- or Stream-like log target
334
+ */
335
+ declare function setLogger(logger?: ILogBasic | ILogLayer<unknown>): void;
336
+ //#endregion
337
+ //#region src/index.d.ts
338
+ /**
339
+ * Helper for deep merging ExportOptions object against library defaults.
340
+ * Exported for unplugin-aphex.
341
+ */
342
+ declare function mergeDefaultExportOptions(options: PartialDeep<ExportOptions> | undefined): ExportOptions;
343
+ type ExportOptions = {
344
+ exportOptions: ExportApplePhotoOptions;
345
+ metadataOptions: 'disabled' | ManageMetadataOptions;
346
+ processOptions: 'disabled' | ProcessImageOptions;
347
+ syncOptions: 'disabled' | SyncOptions;
348
+ };
349
+ declare const defaultSyncOptions: SyncOptions;
350
+ declare const defaultExportOptions: ExportOptions;
351
+ /** Some fields omitted for relevance... */
352
+ type ExportResults = {
353
+ exportResult: Simplify<Omit<ExportApplePhotoResult, 'exportOptions' | 'path' | 'photoInfo'>> | undefined;
354
+ metadataResult: Simplify<Omit<ManageMetadataResult, 'photoInfo'>> | undefined;
355
+ processResult: Simplify<OmitDeep<ProcessImageResult, 'input.path' | 'output.path' | 'path'>> | undefined;
356
+ syncResult: Simplify<Omit<SyncResult['plan'][number], 'photoInfo'>> | undefined;
357
+ };
358
+ type ExportResult = {
359
+ options: ExportOptions;
360
+ path: string;
361
+ photoInfo: PhotoInfo;
362
+ results: ExportResults;
363
+ };
364
+ /**
365
+ * Export a single photo
366
+ */
367
+ declare function exportPhoto(identifier: PhotoInfo | string, destinationDirectory: string, options?: PartialDeep<ExportOptions>): Promise<ExportResult>;
368
+ /**
369
+ * Export photos
370
+ */
371
+ declare function exportPhotos(identifiers: Array<AlbumInfo | PhotoInfo | string>, destinationDirectory: string, options?: PartialDeep<ExportOptions>): Promise<ExportResult[]>;
372
+ //#endregion
373
+ export { ExportOptions, ExportResult, IMAGE_MIME_TYPES, type ImageMimeType, defaultExportOptions, defaultSyncOptions, endExiftool, exportPhoto, exportPhotos, aphexAlbumInfo as getAlbumInfo, aphexPhotoInfo as getPhotoInfo, interactiveSessionStart, interactiveSessionStop, mergeDefaultExportOptions, processImage, setLogger };