@kitschpatrol/aphex 0.1.0 → 0.1.2
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/dist/aphex-swift +0 -0
- package/dist/index.d.ts +37 -21
- package/dist/index.js +55 -38
- package/dist/workers/process-image-worker.js +15 -5
- package/package.json +14 -14
- package/readme.md +1 -1
package/dist/aphex-swift
CHANGED
|
Binary file
|
package/dist/index.d.ts
CHANGED
|
@@ -4,8 +4,9 @@ import { OmitDeep, PartialDeep, Simplify } from "type-fest";
|
|
|
4
4
|
|
|
5
5
|
//#region src/aphex-swift/cli-bridge.d.ts
|
|
6
6
|
/**
|
|
7
|
-
* Start an interactive session with aphex-swift.
|
|
8
|
-
*
|
|
7
|
+
* Start an interactive session with aphex-swift. Commands will be sent to this
|
|
8
|
+
* persistent process instead of spawning new ones.
|
|
9
|
+
*
|
|
9
10
|
* @throws {Error} If the session is already active or fails to start
|
|
10
11
|
*/
|
|
11
12
|
declare function interactiveSessionStart(): Promise<void>;
|
|
@@ -25,8 +26,8 @@ type ResourceInfo = {
|
|
|
25
26
|
width: number;
|
|
26
27
|
};
|
|
27
28
|
/**
|
|
28
|
-
* TypeScript type definition for the JSON representation of a PHAsset
|
|
29
|
-
*
|
|
29
|
+
* TypeScript type definition for the JSON representation of a PHAsset from the
|
|
30
|
+
* iOS Photos framework (CodablePHAsset)
|
|
30
31
|
*/
|
|
31
32
|
type PhotoInfo = {
|
|
32
33
|
dateCreated: Date;
|
|
@@ -49,12 +50,15 @@ type AlbumInfo = {
|
|
|
49
50
|
uuid: string;
|
|
50
51
|
};
|
|
51
52
|
/**
|
|
52
|
-
* Get photo asset information for given identifiers (ID, filename, album name,
|
|
53
|
+
* Get photo asset information for given identifiers (ID, filename, album name,
|
|
54
|
+
* or photo path)
|
|
55
|
+
*
|
|
53
56
|
* @throws {Error} If the command fails
|
|
54
57
|
*/
|
|
55
58
|
declare function aphexPhotoInfo(identifiers: string | string[], caseSensitive?: boolean): Promise<PhotoInfo[]>;
|
|
56
59
|
/**
|
|
57
60
|
* Get album info
|
|
61
|
+
*
|
|
58
62
|
* @throws {Error} If the command fails
|
|
59
63
|
*/
|
|
60
64
|
declare function aphexAlbumInfo(identifiers: string | string[], caseSensitive?: boolean): Promise<AlbumInfo[]>;
|
|
@@ -65,7 +69,8 @@ type ImageMimeType = (typeof IMAGE_MIME_TYPES)[number];
|
|
|
65
69
|
//#endregion
|
|
66
70
|
//#region src/pipeline/engines/applescript-gui.d.ts
|
|
67
71
|
/**
|
|
68
|
-
* Mirrors the export UI in Note that note that some options are contingent on
|
|
72
|
+
* Mirrors the export UI in Note that note that some options are contingent on
|
|
73
|
+
* others.
|
|
69
74
|
*
|
|
70
75
|
* The AppleScript implementation is smart about only "clicking" the necessary
|
|
71
76
|
* options in the GUI.
|
|
@@ -120,8 +125,10 @@ type ExportApplePhotoOptions = {
|
|
|
120
125
|
engineOriginal: ExportEngineOptions;
|
|
121
126
|
engineOriginalAlpha: ExportEngineOptions;
|
|
122
127
|
/**
|
|
123
|
-
* Append a few digits from the image's local identifier in Photos.app, useful
|
|
124
|
-
*
|
|
128
|
+
* Append a few digits from the image's local identifier in Photos.app, useful
|
|
129
|
+
* to avoid name collisions if exporting multiple album-worth of photos. Only
|
|
130
|
+
* applies to `FileNameOptions` `title` or `uuid`. This is NOT the same as a
|
|
131
|
+
* content hash.
|
|
125
132
|
*/
|
|
126
133
|
fileNameAppendUuidFragment: boolean;
|
|
127
134
|
fileNameNormalizeExtensions: boolean;
|
|
@@ -154,8 +161,8 @@ type ImageTags = {
|
|
|
154
161
|
credit?: string | undefined; /** Description, used as alt text in HTML content */
|
|
155
162
|
description?: string | undefined;
|
|
156
163
|
/**
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
* Image Type, e.g. 'animation', 'diagram', 'illustration', 'screenshot',
|
|
165
|
+
* 'image', 'photo', 'render', 'video'
|
|
159
166
|
*/
|
|
160
167
|
label?: Label | undefined;
|
|
161
168
|
preservedFileName?: string | undefined;
|
|
@@ -192,20 +199,24 @@ type LossyFormat = 'avif' | 'jpeg' | 'none' | 'webp';
|
|
|
192
199
|
* Configuration options for image compression.
|
|
193
200
|
*
|
|
194
201
|
* The compression process follows a three-tier approach:
|
|
202
|
+
*
|
|
195
203
|
* 1. First attempts lossless compression if enabled
|
|
196
204
|
* 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
|
|
205
|
+
* 3. Finally uses lossy compression if other methods fail to meet size
|
|
206
|
+
* requirements
|
|
198
207
|
*
|
|
199
208
|
* Set format to 'none' to disable that compression tier.
|
|
200
209
|
*/
|
|
201
210
|
type CompressImageOptions = {
|
|
202
211
|
/**
|
|
203
|
-
* Force compression even if the original image is already within the size
|
|
204
|
-
* When false, images smaller than maxFileSizeBytes are copied without
|
|
212
|
+
* Force compression even if the original image is already within the size
|
|
213
|
+
* limit. When false, images smaller than maxFileSizeBytes are copied without
|
|
214
|
+
* modification.
|
|
205
215
|
*/
|
|
206
216
|
forceCompression: boolean;
|
|
207
217
|
/**
|
|
208
218
|
* Format to use for lossless compression (first attempt).
|
|
219
|
+
*
|
|
209
220
|
* - 'avif': Best compression but slower encoding
|
|
210
221
|
* - 'png': Good compatibility, optimized with oxipng
|
|
211
222
|
* - 'webp': Good balance of compression and speed
|
|
@@ -214,6 +225,7 @@ type CompressImageOptions = {
|
|
|
214
225
|
losslessFormat: LosslessFormat;
|
|
215
226
|
/**
|
|
216
227
|
* Format to use for lossy compression (final fallback).
|
|
228
|
+
*
|
|
217
229
|
* - 'avif': Best compression but very slow encoding
|
|
218
230
|
* - 'jpeg': Good compatibility, uses mozjpeg encoder
|
|
219
231
|
* - 'webp': Good balance of compression and speed
|
|
@@ -222,19 +234,21 @@ type CompressImageOptions = {
|
|
|
222
234
|
lossyFormat: LossyFormat;
|
|
223
235
|
/**
|
|
224
236
|
* Quality level for lossy compression (0-1 scale).
|
|
237
|
+
*
|
|
225
238
|
* - 0: Lowest quality, smallest file size
|
|
226
239
|
* - 1: Highest quality, largest file size
|
|
227
240
|
* - Recommended range: 0.85-0.96 for high-quality images
|
|
228
241
|
*/
|
|
229
242
|
lossyQuality: number;
|
|
230
243
|
/**
|
|
231
|
-
* Maximum allowed file size in bytes.
|
|
232
|
-
*
|
|
233
|
-
*
|
|
244
|
+
* Maximum allowed file size in bytes. Images exceeding this size will be
|
|
245
|
+
* compressed using the configured formats. The compression process stops when
|
|
246
|
+
* this target is met or all options are exhausted.
|
|
234
247
|
*/
|
|
235
248
|
maxFileSizeBytes: number;
|
|
236
249
|
/**
|
|
237
250
|
* Format to use for near-lossless compression (second attempt).
|
|
251
|
+
*
|
|
238
252
|
* - 'webp': Only format currently supporting near-lossless mode
|
|
239
253
|
* - 'none': Skip near-lossless compression
|
|
240
254
|
*/
|
|
@@ -321,16 +335,18 @@ type SyncResult = {
|
|
|
321
335
|
/**
|
|
322
336
|
* End the exiftool process gracefully
|
|
323
337
|
*
|
|
324
|
-
* This should be called when you're done using exiftool to prevent
|
|
325
|
-
*
|
|
338
|
+
* This should be called when you're done using exiftool to prevent hanging
|
|
339
|
+
* processes. It's safe to call multiple times.
|
|
326
340
|
*/
|
|
327
341
|
declare function endExiftool(): Promise<void>;
|
|
328
342
|
//#endregion
|
|
329
343
|
//#region src/utilities/log.d.ts
|
|
330
344
|
/**
|
|
331
|
-
* Set the logger instance for the module.
|
|
332
|
-
*
|
|
333
|
-
*
|
|
345
|
+
* Set the logger instance for the module. Export this for library consumers to
|
|
346
|
+
* inject their own logger.
|
|
347
|
+
*
|
|
348
|
+
* @param logger - Accepts either a LogLayer instance or a Console- or
|
|
349
|
+
* Stream-like log target
|
|
334
350
|
*/
|
|
335
351
|
declare function setLogger(logger?: ILogBasic | ILogLayer<unknown>): void;
|
|
336
352
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -20,9 +20,11 @@ import { deepEqual } from "fast-equals";
|
|
|
20
20
|
*/
|
|
21
21
|
let log = createLogger("@kitschpatrol/aphex");
|
|
22
22
|
/**
|
|
23
|
-
* Set the logger instance for the module.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Set the logger instance for the module. Export this for library consumers to
|
|
24
|
+
* inject their own logger.
|
|
25
|
+
*
|
|
26
|
+
* @param logger - Accepts either a LogLayer instance or a Console- or
|
|
27
|
+
* Stream-like log target
|
|
26
28
|
*/
|
|
27
29
|
function setLogger(logger) {
|
|
28
30
|
log = injectionHelper(logger);
|
|
@@ -33,8 +35,8 @@ function setLogger(logger) {
|
|
|
33
35
|
* Centralized exiftool instance management
|
|
34
36
|
*
|
|
35
37
|
* This module provides a managed exiftool instance with proper lifecycle
|
|
36
|
-
* management to prevent hanging processes. The exiftool-vendored library
|
|
37
|
-
*
|
|
38
|
+
* management to prevent hanging processes. The exiftool-vendored library spawns
|
|
39
|
+
* persistent Perl processes that must be explicitly ended.
|
|
38
40
|
*/
|
|
39
41
|
const exiftoolInstance = new ExifTool({
|
|
40
42
|
maxProcs: 4,
|
|
@@ -52,8 +54,8 @@ function getExiftool() {
|
|
|
52
54
|
/**
|
|
53
55
|
* End the exiftool process gracefully
|
|
54
56
|
*
|
|
55
|
-
* This should be called when you're done using exiftool to prevent
|
|
56
|
-
*
|
|
57
|
+
* This should be called when you're done using exiftool to prevent hanging
|
|
58
|
+
* processes. It's safe to call multiple times.
|
|
57
59
|
*/
|
|
58
60
|
async function endExiftool() {
|
|
59
61
|
if (exiftoolInstance.pids.length > 0) {
|
|
@@ -65,8 +67,8 @@ async function endExiftool() {
|
|
|
65
67
|
/**
|
|
66
68
|
* Install process exit handlers for automatic cleanup
|
|
67
69
|
*
|
|
68
|
-
* This ensures exiftool processes are cleaned up even if the user
|
|
69
|
-
*
|
|
70
|
+
* This ensures exiftool processes are cleaned up even if the user forgets to
|
|
71
|
+
* call endExiftool(). Only installs handlers once.
|
|
70
72
|
*/
|
|
71
73
|
function scheduleProcessCleanup() {
|
|
72
74
|
if (processExitHandlerInstalled) return;
|
|
@@ -107,9 +109,8 @@ async function getSizeBytes(filePath) {
|
|
|
107
109
|
return size;
|
|
108
110
|
}
|
|
109
111
|
/**
|
|
110
|
-
* Normalizes file extensions, particularly for image files
|
|
111
|
-
* Converts to
|
|
112
|
-
* Converts .jpg to .jpeg and .tif to .tiff
|
|
112
|
+
* Normalizes file extensions, particularly for image files Converts to
|
|
113
|
+
* lowercase. Converts .jpg to .jpeg and .tif to .tiff
|
|
113
114
|
*/
|
|
114
115
|
function normalizeExtension(filePath) {
|
|
115
116
|
const directory = path.dirname(filePath);
|
|
@@ -123,7 +124,9 @@ function normalizeExtension(filePath) {
|
|
|
123
124
|
}
|
|
124
125
|
/**
|
|
125
126
|
* Converts a filename to a GitHub-style slug format
|
|
127
|
+
*
|
|
126
128
|
* @param filePath - The path of the file
|
|
129
|
+
*
|
|
127
130
|
* @returns The slug version of the filename without extension
|
|
128
131
|
*/
|
|
129
132
|
function getSlugFilename(filePath) {
|
|
@@ -132,7 +135,9 @@ function getSlugFilename(filePath) {
|
|
|
132
135
|
/**
|
|
133
136
|
* Ensures the directory for a given file path exists, creating it if necessary.
|
|
134
137
|
* Expands '~' to the user's home directory.
|
|
138
|
+
*
|
|
135
139
|
* @param filePath - The full file path for which to ensure the directory exists
|
|
140
|
+
*
|
|
136
141
|
* @returns The fully expanded directory path which definitely exists
|
|
137
142
|
*/
|
|
138
143
|
async function ensureDirectoryExists(filePath) {
|
|
@@ -141,7 +146,8 @@ async function ensureDirectoryExists(filePath) {
|
|
|
141
146
|
return expandedDirectory;
|
|
142
147
|
}
|
|
143
148
|
/**
|
|
144
|
-
* Creates a temporary directory with an optional custom prefix based on
|
|
149
|
+
* Creates a temporary directory with an optional custom prefix based on
|
|
150
|
+
* provided labels
|
|
145
151
|
*/
|
|
146
152
|
async function getTempDirectory(...labels) {
|
|
147
153
|
const prefix = labels.length > 0 ? `com.kitschpatrol.aphex.${labels.map((l) => l.toLowerCase()).join(".")}.` : "com.kitschpatrol.aphex.";
|
|
@@ -188,8 +194,10 @@ function assertSingleElement(array) {
|
|
|
188
194
|
//#endregion
|
|
189
195
|
//#region src/utilities/paths.ts
|
|
190
196
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
197
|
+
* Import.meta.dirname for Node >= 20.19.0
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* getDirname(import.meta)
|
|
193
201
|
*/
|
|
194
202
|
function getDirname(meta) {
|
|
195
203
|
return dirname(fileURLToPath(process.env.PISCINA_WORKER_META_URL ?? meta.url));
|
|
@@ -209,7 +217,8 @@ function getPackageWorkersPath(meta) {
|
|
|
209
217
|
return path.basename(dirname) === "dist" ? path.join(dirname, "workers") : path.join(dirname, "..", "workers");
|
|
210
218
|
}
|
|
211
219
|
/**
|
|
212
|
-
* Binary directory path of package or distribution folder (Copied by native
|
|
220
|
+
* Binary directory path of package or distribution folder (Copied by native
|
|
221
|
+
* build script)
|
|
213
222
|
*/
|
|
214
223
|
function getPackageBinPath(meta) {
|
|
215
224
|
const dirname = getDirname(meta);
|
|
@@ -221,8 +230,9 @@ const SHELL_SPECIAL_CHARS_REGEX = /[\s'"\\]/;
|
|
|
221
230
|
let interactiveProcess;
|
|
222
231
|
let currentRequest;
|
|
223
232
|
/**
|
|
224
|
-
* Start an interactive session with aphex-swift.
|
|
225
|
-
*
|
|
233
|
+
* Start an interactive session with aphex-swift. Commands will be sent to this
|
|
234
|
+
* persistent process instead of spawning new ones.
|
|
235
|
+
*
|
|
226
236
|
* @throws {Error} If the session is already active or fails to start
|
|
227
237
|
*/
|
|
228
238
|
async function interactiveSessionStart() {
|
|
@@ -386,7 +396,9 @@ function assertAlbumInfoArray(value) {
|
|
|
386
396
|
if (!isAlbumInfoArray(value)) throw new Error("Invalid AlbumInfo array");
|
|
387
397
|
}
|
|
388
398
|
/**
|
|
389
|
-
* Get photo asset information for given identifiers (ID, filename, album name,
|
|
399
|
+
* Get photo asset information for given identifiers (ID, filename, album name,
|
|
400
|
+
* or photo path)
|
|
401
|
+
*
|
|
390
402
|
* @throws {Error} If the command fails
|
|
391
403
|
*/
|
|
392
404
|
async function aphexPhotoInfo(identifiers, caseSensitive = false) {
|
|
@@ -410,6 +422,7 @@ async function aphexPhotoInfo(identifiers, caseSensitive = false) {
|
|
|
410
422
|
}
|
|
411
423
|
/**
|
|
412
424
|
* Get album info
|
|
425
|
+
*
|
|
413
426
|
* @throws {Error} If the command fails
|
|
414
427
|
*/
|
|
415
428
|
async function aphexAlbumInfo(identifiers, caseSensitive = false) {
|
|
@@ -433,6 +446,7 @@ async function aphexAlbumInfo(identifiers, caseSensitive = false) {
|
|
|
433
446
|
}
|
|
434
447
|
/**
|
|
435
448
|
* Export photos for given identifiers to a destination directory
|
|
449
|
+
*
|
|
436
450
|
* @throws {Error} If the command fails
|
|
437
451
|
*/
|
|
438
452
|
async function aphexExport(identifiers, destination, caseSensitive = false, originals = false) {
|
|
@@ -464,7 +478,8 @@ function dateReviver$1(key, value) {
|
|
|
464
478
|
//#endregion
|
|
465
479
|
//#region src/aphex-swift/identifiers.ts
|
|
466
480
|
/**
|
|
467
|
-
* Takes a mix of photos, albums, strings, all resolved to a single array of
|
|
481
|
+
* Takes a mix of photos, albums, strings, all resolved to a single array of
|
|
482
|
+
* PhotoInfo objects
|
|
468
483
|
*/
|
|
469
484
|
async function resolveIdentifiers(identifiers) {
|
|
470
485
|
const photoInfos = identifiers.filter((identifier) => isPhotoInfo(identifier));
|
|
@@ -892,8 +907,10 @@ function escapeForAppleScript(text) {
|
|
|
892
907
|
}
|
|
893
908
|
/**
|
|
894
909
|
* Export a photo via AppleScript using the Photos app's scripting dictionary
|
|
910
|
+
*
|
|
895
911
|
* @param photoUuid - UUID of the photo to export
|
|
896
912
|
* @param forceOriginal - Whether to force export of original version
|
|
913
|
+
*
|
|
897
914
|
* @returns Promise<string> - Path to the exported file
|
|
898
915
|
*/
|
|
899
916
|
async function exportViaAppleScript(photoUuid, forceOriginal = false) {
|
|
@@ -941,8 +958,8 @@ const defaultExportViaAppleScriptGuiOptions = {
|
|
|
941
958
|
tiffBitDepth: 8
|
|
942
959
|
};
|
|
943
960
|
/**
|
|
944
|
-
* Export a photo via the Photos.app GUI to a temporary directory
|
|
945
|
-
*
|
|
961
|
+
* Export a photo via the Photos.app GUI to a temporary directory Clean up and
|
|
962
|
+
* move the file as needed afterwards
|
|
946
963
|
*/
|
|
947
964
|
async function exportViaAppleScriptGui(uuid, options) {
|
|
948
965
|
const tempDirectory = await getTempDirectory("engine", "applescript-gui");
|
|
@@ -973,8 +990,8 @@ async function exportViaAppleScriptGui(uuid, options) {
|
|
|
973
990
|
//#endregion
|
|
974
991
|
//#region src/pipeline/engines/file-system.ts
|
|
975
992
|
/**
|
|
976
|
-
* Export a photo via direct file system copy of the original or edited file to
|
|
977
|
-
* Clean up and move the file as needed afterwards
|
|
993
|
+
* Export a photo via direct file system copy of the original or edited file to
|
|
994
|
+
* a temporary directory Clean up and move the file as needed afterwards
|
|
978
995
|
*/
|
|
979
996
|
async function exportViaFileSystem(photoUuid, forceOriginal = false) {
|
|
980
997
|
const [{ edited, original }] = isPhotoInfo(photoUuid) ? [photoUuid] : await aphexPhotoInfo(photoUuid);
|
|
@@ -1136,10 +1153,9 @@ async function getEngineForPhoto(photoInfo, options) {
|
|
|
1136
1153
|
return engine;
|
|
1137
1154
|
}
|
|
1138
1155
|
/**
|
|
1139
|
-
* Export a single album to a folder
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1142
|
-
* Does NOT further process the images
|
|
1156
|
+
* Export a single album to a folder Optionally sync the output directory with
|
|
1157
|
+
* the album, deleting any images that are no longer in the album Optionally
|
|
1158
|
+
* audit the album for duplicate titles Does NOT further process the images
|
|
1143
1159
|
*/
|
|
1144
1160
|
//#endregion
|
|
1145
1161
|
//#region src/pipeline/image-metadata.ts
|
|
@@ -1207,8 +1223,9 @@ async function optimizePng(imagePath, destinationDirectory) {
|
|
|
1207
1223
|
return destinationImagePath;
|
|
1208
1224
|
}
|
|
1209
1225
|
/**
|
|
1210
|
-
* Resize a PNG image to fit within a maximum width and height
|
|
1211
|
-
*
|
|
1226
|
+
* Resize a PNG image to fit within a maximum width and height Copies the image
|
|
1227
|
+
* unchanged if it already fits within the target size
|
|
1228
|
+
*
|
|
1212
1229
|
* @returns The destination image path
|
|
1213
1230
|
*/
|
|
1214
1231
|
async function resizePngToFit(imagePath, destinationDirectory, maxWidth, maxHeight) {
|
|
@@ -1314,8 +1331,8 @@ async function convertToWebp(sourceImagePath, destinationDirectory, quality, for
|
|
|
1314
1331
|
return destinationImagePath;
|
|
1315
1332
|
}
|
|
1316
1333
|
/**
|
|
1317
|
-
* Convert an image to a PNG
|
|
1318
|
-
*
|
|
1334
|
+
* Convert an image to a PNG Supports width / height to help quality comparison
|
|
1335
|
+
* tools match target sizes in one step
|
|
1319
1336
|
*/
|
|
1320
1337
|
async function convertToPng(sourceImagePath, destinationDirectory, force = false, width, height) {
|
|
1321
1338
|
const mime = lookupImageMimeType(sourceImagePath, true);
|
|
@@ -1347,9 +1364,9 @@ async function convertToPng(sourceImagePath, destinationDirectory, force = false
|
|
|
1347
1364
|
return destinationImagePath;
|
|
1348
1365
|
}
|
|
1349
1366
|
/**
|
|
1350
|
-
* Convert an image to a TGA
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1367
|
+
* Convert an image to a TGA Only viable input to mozjpeg is TGA, BMP, and some
|
|
1368
|
+
* other esoteric stuff... BMP emitted by sips is too modern for cjpeg, so we
|
|
1369
|
+
* need to convert to TGA instead
|
|
1353
1370
|
*/
|
|
1354
1371
|
async function convertToTga(sourceImagePath, destinationDirectory, force = false) {
|
|
1355
1372
|
const mime = lookupImageMimeType(sourceImagePath, [
|
|
@@ -1495,9 +1512,8 @@ async function compressImage(sourceImagePath, destinationDirectory, options) {
|
|
|
1495
1512
|
const SSIM_REGEX = /All:\s*([\d.]+)/;
|
|
1496
1513
|
const PSNR_REGEX = /average:\s*([\d.]+)/;
|
|
1497
1514
|
/**
|
|
1498
|
-
* Get two identically sized PNGs from two images.
|
|
1499
|
-
*
|
|
1500
|
-
* Remember to clean up temp
|
|
1515
|
+
* Get two identically sized PNGs from two images. Uses the smaller image size
|
|
1516
|
+
* as the target size. Remember to clean up temp
|
|
1501
1517
|
*/
|
|
1502
1518
|
async function getTwoIdenticallySizedPng(image1, image2) {
|
|
1503
1519
|
const tempDirectory1 = await fse.mkdtemp(path.join(os.tmpdir(), `com.kitschpatrol.aphex.get-two-identically-sized-png.${getSlugFilename(image1)}.`));
|
|
@@ -1816,6 +1832,7 @@ async function getSyncPlanForImages(identifiers, destinationDirectory, options,
|
|
|
1816
1832
|
}
|
|
1817
1833
|
/**
|
|
1818
1834
|
* See if there's a difference between the source image and the target image
|
|
1835
|
+
*
|
|
1819
1836
|
* @returns Undefined if the, or the reason for the difference if found
|
|
1820
1837
|
*/
|
|
1821
1838
|
async function isDifferent(diffStrategies, sourcePhotoInfo, sourcePhotoFileBaseName, matchPhotoFilePath, matchPhotoTags, exportOptions, forceUpdate) {
|
|
@@ -7,14 +7,22 @@
|
|
|
7
7
|
* Worker function for processing images in a separate thread
|
|
8
8
|
*
|
|
9
9
|
* Note: Each worker spawns its own exiftool instance via the centralized
|
|
10
|
-
* exiftool module. These are managed by Piscina's worker lifecycle - when
|
|
11
|
-
*
|
|
10
|
+
* exiftool module. These are managed by Piscina's worker lifecycle - when the
|
|
11
|
+
* worker terminates, the exiftool process exit handlers will clean up.
|
|
12
|
+
*
|
|
12
13
|
* @param {object} params - Worker parameters
|
|
13
|
-
* @param {string} params.destinationDirectory - Directory where processed image
|
|
14
|
-
*
|
|
14
|
+
* @param {string} params.destinationDirectory - Directory where processed image
|
|
15
|
+
* will be saved
|
|
16
|
+
* @param {import('../pipeline/image-process.ts').ProcessImageOptions} params.options
|
|
17
|
+
* - Image processing options
|
|
18
|
+
*
|
|
15
19
|
* @param {string} params.sourceImagePath - Path to the source image file
|
|
16
20
|
* @param {boolean} params.verbose - Whether to log verbose output
|
|
17
|
-
*
|
|
21
|
+
*
|
|
22
|
+
* @returns {Promise<
|
|
23
|
+
* import('../pipeline/image-process.ts').ProcessImageResult
|
|
24
|
+
* >}
|
|
25
|
+
* Processing result with input/output info and report
|
|
18
26
|
*/
|
|
19
27
|
export default async function worker({ destinationDirectory, options, sourceImagePath, verbose }) {
|
|
20
28
|
// Weird workaround after issues with ESM imports in worker threads and more recent versions of Node / TSX / Piscina / etc?
|
|
@@ -26,11 +34,13 @@ export default async function worker({ destinationDirectory, options, sourceImag
|
|
|
26
34
|
if (verbose) {
|
|
27
35
|
console.log(`Processing image on worker thread:\n${sourceImagePath}`)
|
|
28
36
|
}
|
|
37
|
+
|
|
29
38
|
const result = await processImage(sourceImagePath, destinationDirectory, options)
|
|
30
39
|
if (verbose) {
|
|
31
40
|
console.log(
|
|
32
41
|
`Finished processing image on worker thread:\n${sourceImagePath}\nTime:\n${result.report.durationMs / 1000} seconds`,
|
|
33
42
|
)
|
|
34
43
|
}
|
|
44
|
+
|
|
35
45
|
return result
|
|
36
46
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitschpatrol/aphex",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Apple Photos Export. TypeScript library and CLI tool to export and process images and albums from your macOS Photos.app library.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apple",
|
|
@@ -39,38 +39,38 @@
|
|
|
39
39
|
"dist/*"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@sindresorhus/is": "^8.
|
|
42
|
+
"@sindresorhus/is": "^8.1.0",
|
|
43
43
|
"defu": "^6.1.7",
|
|
44
44
|
"execa": "^9.6.1",
|
|
45
|
-
"exiftool-vendored": "^35.
|
|
45
|
+
"exiftool-vendored": "^35.20.0",
|
|
46
46
|
"fast-equals": "^6.0.0",
|
|
47
|
-
"fs-extra": "^11.3.
|
|
47
|
+
"fs-extra": "^11.3.5",
|
|
48
48
|
"github-slugger": "^2.0.0",
|
|
49
49
|
"image-size": "^2.0.2",
|
|
50
50
|
"importx": "^0.5.2",
|
|
51
|
-
"lognow": "^0.6.
|
|
51
|
+
"lognow": "^0.6.2",
|
|
52
52
|
"mrmime": "^2.0.1",
|
|
53
53
|
"piscina": "^5.1.4",
|
|
54
54
|
"type-fest": "^5.6.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@arethetypeswrong/core": "^0.18.2",
|
|
58
|
-
"@clack/prompts": "^1.
|
|
59
|
-
"@kitschpatrol/shared-config": "^7.
|
|
58
|
+
"@clack/prompts": "^1.4.0",
|
|
59
|
+
"@kitschpatrol/shared-config": "^7.6.0",
|
|
60
60
|
"@types/fs-extra": "^11.0.4",
|
|
61
61
|
"@types/node": "~22.18.13",
|
|
62
|
-
"@vitest/coverage-v8": "^4.1.
|
|
63
|
-
"bumpp": "^11.0
|
|
62
|
+
"@vitest/coverage-v8": "^4.1.6",
|
|
63
|
+
"bumpp": "^11.1.0",
|
|
64
64
|
"globby": "^16.2.0",
|
|
65
65
|
"markdown-table": "^3.0.4",
|
|
66
66
|
"mdat-plugin-cli-help": "^3.0.0",
|
|
67
67
|
"open": "^11.0.0",
|
|
68
68
|
"pretty-bytes": "^7.1.0",
|
|
69
69
|
"pretty-ms": "^9.3.0",
|
|
70
|
-
"publint": "^0.3.
|
|
71
|
-
"tsdown": "^0.
|
|
72
|
-
"tsx": "^4.
|
|
73
|
-
"vitest": "^4.1.
|
|
70
|
+
"publint": "^0.3.21",
|
|
71
|
+
"tsdown": "^0.22.0",
|
|
72
|
+
"tsx": "^4.22.0",
|
|
73
|
+
"vitest": "^4.1.6"
|
|
74
74
|
},
|
|
75
75
|
"engines": {
|
|
76
76
|
"node": ">=22.18.0"
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"clean": "git rm -f pnpm-lock.yaml && git clean -fdX -e !.claude/",
|
|
93
93
|
"fix": "ksc fix && mdat ./native/aphex-swift/readme.md",
|
|
94
94
|
"lint": "ksc lint",
|
|
95
|
-
"release": "bumpp --commit 'Release: %s' && pnpm build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token')
|
|
95
|
+
"release": "bumpp --commit 'Release: %s' && pnpm build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') pnpm publish",
|
|
96
96
|
"test": "vitest run",
|
|
97
97
|
"test:coverage": "vitest run --coverage"
|
|
98
98
|
}
|
package/readme.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<!-- badges -->
|
|
10
10
|
|
|
11
11
|
[](https://npmjs.com/package/@kitschpatrol/aphex)
|
|
12
|
-
[](https://opensource.org/
|
|
12
|
+
[](https://opensource.org/license/mit/)
|
|
13
13
|
|
|
14
14
|
<!-- /badges -->
|
|
15
15
|
|