@thi.ng/imago 0.1.0 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2024-02-22T11:59:16Z
3
+ - **Last updated**: 2024-02-22T23:15:26Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,14 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/imago@0.2.0) (2024-02-22)
13
+
14
+ #### 🚀 Features
15
+
16
+ - add support for custom path part replacements ([b0419e1](https://github.com/thi-ng/umbrella/commit/b0419e1))
17
+ - add more path part replacements ([9f84a8a](https://github.com/thi-ng/umbrella/commit/9f84a8a))
18
+ - collect all output paths, update processImage() result ([a3ca52f](https://github.com/thi-ng/umbrella/commit/a3ca52f))
19
+
12
20
  ## [0.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/imago@0.1.0) (2024-02-22)
13
21
 
14
22
  #### 🚀 Features
package/README.md CHANGED
@@ -42,6 +42,7 @@
42
42
  - [hsbl](#hsbl)
43
43
  - [nest](#nest)
44
44
  - [output](#output)
45
+ - [Templated output paths](#templated-output-paths)
45
46
  - [resize](#resize)
46
47
  - [rotate](#rotate)
47
48
  - [Status](#status)
@@ -219,6 +220,36 @@ File output in any of these formats:
219
220
  - tiff
220
221
  - webp
221
222
 
223
+ #### Templated output paths
224
+
225
+ Output paths can contain `{id}`-templated parts which will be replaced/expanded.
226
+ The following built-in IDs are supported and custom IDs will be looked up via
227
+ the
228
+ [pathParts](https://docs.thi.ng/umbrella/imago/interfaces/ImgProcOpts.html#pathParts)
229
+ options provided to
230
+ [processImage()](https://docs.thi.ng/umbrella/imago/functions/processImage.html).
231
+ Any others will remain as is. Custom IDs take precedence over built-in ones.
232
+
233
+ - `name`: original base filename (w/o ext)
234
+ - `sha1`/`sha224`/`sha256`/`sha384`/`sha512`: truncated hash of output (8 chars)
235
+ - `w`: current image width
236
+ - `h`: current image height
237
+ - `date`: yyyyMMdd date format, e.g. 20240223
238
+ - `time`: HHmmss time format, e.g. 234459
239
+ - `year`: 4-digit year
240
+ - `month`: 2-digit month
241
+ - `week`: 2-digit week
242
+ - `day`: 2-digit day in month
243
+ - `hour`: 2-digit hour (24h system)
244
+ - `minute`: 2-digit minute
245
+ - `second`: 2-digit second
246
+
247
+ Output paths can contain sub-directories which will be automatically created
248
+ (relative to the [configured output
249
+ dir](https://docs.thi.ng/umbrella/imago/interfaces/ImgProcOpts.html#outDir)).
250
+ For example, the path template `{year}/{month}/{day}/{name}-{sha1}.jpg` might
251
+ get replaced to: `2024/02/22/test-123cafe4.jpg`...
252
+
222
253
  ### resize
223
254
 
224
255
  Resizing image
@@ -250,7 +281,7 @@ For Node.js REPL:
250
281
  const imago = await import("@thi.ng/imago");
251
282
  ```
252
283
 
253
- Package sizes (brotli'd, pre-treeshake): ESM: 3.13 KB
284
+ Package sizes (brotli'd, pre-treeshake): ESM: 3.29 KB
254
285
 
255
286
  ## Dependencies
256
287
 
package/api.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { Keys } from "@thi.ng/api";
1
+ /// <reference types="node" />
2
+ import type { Fn3, Keys, TypedArray } from "@thi.ng/api";
2
3
  import type { ILogger } from "@thi.ng/logger";
3
4
  import type { AvifOptions, Blend, Exif, ExtendWith, FitEnum, GifOptions, Jp2Options, JpegOptions, JxlOptions, KernelEnum, Metadata, PngOptions, TiffOptions, TileOptions, WebpOptions } from "sharp";
4
5
  export type Gravity = "c" | "e" | "n" | "ne" | "nw" | "s" | "se" | "sw" | "w";
@@ -147,6 +148,17 @@ export interface ImgProcOpts {
147
148
  * Base directory for {@link output} steps
148
149
  */
149
150
  outDir: string;
151
+ /**
152
+ * An object with custom output path replacements for {@link formatPath}. If
153
+ * a given replacement value is a function, it will be called with the
154
+ * current {@link ImgProcCtx}, the current {@link OutputSpec} (e.g. to
155
+ * obtain configured options) and the already serialized image as buffer.
156
+ *
157
+ * @remarks
158
+ * Replacement IDs in this object will take precedence over built-in
159
+ * replacement IDs, e.g. allowing to override `name`, `date` etc.
160
+ */
161
+ pathParts: Record<string, Fn3<ImgProcCtx, OutputSpec, Buffer | TypedArray, string> | string>;
150
162
  }
151
163
  export interface ImgProcCtx {
152
164
  path?: string;
@@ -155,6 +167,10 @@ export interface ImgProcCtx {
155
167
  meta: Metadata;
156
168
  logger: ILogger;
157
169
  opts: Partial<ImgProcOpts>;
170
+ /**
171
+ * Paths of all exported images.
172
+ */
173
+ outputs: string[];
158
174
  }
159
175
  export declare const GRAVITY_POSITION: Record<Gravity, string>;
160
176
  export declare const GRAVITY_MAP: Record<Gravity, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/imago",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "JSON & API-based declarative and extensible image processing trees/pipelines",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -37,18 +37,18 @@
37
37
  "dependencies": {
38
38
  "@thi.ng/api": "^8.9.25",
39
39
  "@thi.ng/checks": "^3.5.0",
40
- "@thi.ng/date": "^2.6.0",
40
+ "@thi.ng/date": "^2.7.0",
41
41
  "@thi.ng/defmulti": "^3.0.25",
42
42
  "@thi.ng/errors": "^2.4.18",
43
- "@thi.ng/file-io": "^1.3.2",
43
+ "@thi.ng/file-io": "^1.3.3",
44
44
  "@thi.ng/logger": "^3.0.2",
45
- "@thi.ng/pixel": "^6.1.10",
46
- "@thi.ng/pixel-dither": "^1.1.108",
45
+ "@thi.ng/pixel": "^6.1.11",
46
+ "@thi.ng/pixel-dither": "^1.1.109",
47
47
  "sharp": "^0.33.2"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@microsoft/api-extractor": "^7.40.1",
51
- "@thi.ng/vectors": "^7.10.10",
51
+ "@thi.ng/vectors": "^7.10.11",
52
52
  "esbuild": "^0.20.0",
53
53
  "rimraf": "^5.0.5",
54
54
  "typedoc": "^0.25.7",
@@ -121,5 +121,5 @@
121
121
  "status": "alpha",
122
122
  "year": 2024
123
123
  },
124
- "gitHead": "4513a1c703bdbf0f0867f03e547e47692e415fac\n"
124
+ "gitHead": "16f2b92b5410bd35dcde6c2971c8e62783ebc472\n"
125
125
  }
package/path.d.ts CHANGED
@@ -1,22 +1,34 @@
1
1
  /// <reference types="node" />
2
2
  import type { TypedArray } from "@thi.ng/api";
3
- import type { ImgProcCtx } from "./api.js";
3
+ import type { ImgProcCtx, OutputSpec } from "./api.js";
4
4
  /**
5
5
  * Expands/replaces all `{xyz}`-templated identifiers in given file path.
6
6
  *
7
7
  * @remarks
8
- * The following IDs are supported. Any others will remain as is.
8
+ * The following built-in IDs are supported and custom IDs will be looked up via
9
+ * the {@link ImgProcOpts.pathParts} options provided to {@link processImage}.
10
+ * Any others will remain as is. Custom IDs take precedence over built-in ones.
9
11
  *
10
- * - date: yyyyMMdd
11
- * - time: HHmmss
12
12
  * - name: original base filename (w/o ext)
13
13
  * - sha1/224/256/384/512: truncated hash of output
14
14
  * - w: current width
15
15
  * - h: current height
16
+ * - date: yyyyMMdd
17
+ * - time: HHmmss
18
+ * - year: 4-digit year
19
+ * - month: 2-digit month
20
+ * - week: 2-digit week
21
+ * - day: 2-digit day in month
22
+ * - hour: 2-digit hour (24h system)
23
+ * - minute: 2-digit minute
24
+ * - second: 2-digit second
25
+ *
26
+ * All date/time related values will be in UTC.
16
27
  *
17
28
  * @param path
18
- * @param buf
19
29
  * @param ctx
30
+ * @param spec
31
+ * @param buf
20
32
  */
21
- export declare const formatPath: (path: string, buf: Buffer | TypedArray, ctx: ImgProcCtx) => string;
33
+ export declare const formatPath: (path: string, ctx: ImgProcCtx, spec: OutputSpec, buf: Buffer | TypedArray) => string;
22
34
  //# sourceMappingURL=path.d.ts.map
package/path.js CHANGED
@@ -1,8 +1,24 @@
1
- import { FMT_HHmmss_ALT, FMT_yyyyMMdd_ALT } from "@thi.ng/date";
1
+ import { isFunction } from "@thi.ng/checks";
2
+ import {
3
+ FMT_HH,
4
+ FMT_HHmmss_ALT,
5
+ FMT_MM,
6
+ FMT_dd,
7
+ FMT_mm,
8
+ FMT_ss,
9
+ FMT_ww,
10
+ FMT_yyyy,
11
+ FMT_yyyyMMdd_ALT
12
+ } from "@thi.ng/date";
2
13
  import { illegalArgs as unsupported } from "@thi.ng/errors";
3
14
  import { createHash } from "node:crypto";
4
15
  import { basename } from "node:path";
5
- const formatPath = (path, buf, ctx) => path.replace(/\{(\w+)\}/g, (match, id) => {
16
+ const _ = void 0;
17
+ const formatPath = (path, ctx, spec, buf) => path.replace(/\{(\w+)\}/g, (match, id) => {
18
+ const custom = ctx.opts.pathParts?.[id];
19
+ if (custom != null) {
20
+ return isFunction(custom) ? custom(ctx, spec, buf) : custom;
21
+ }
6
22
  switch (id) {
7
23
  case "name": {
8
24
  !path && unsupported(
@@ -23,9 +39,23 @@ const formatPath = (path, buf, ctx) => path.replace(/\{(\w+)\}/g, (match, id) =>
23
39
  case "h":
24
40
  return String(ctx.size[1]);
25
41
  case "date":
26
- return FMT_yyyyMMdd_ALT();
42
+ return FMT_yyyyMMdd_ALT(_, true);
27
43
  case "time":
28
- return FMT_HHmmss_ALT();
44
+ return FMT_HHmmss_ALT(_, true);
45
+ case "year":
46
+ return FMT_yyyy(_, true);
47
+ case "month":
48
+ return FMT_MM(_, true);
49
+ case "week":
50
+ return FMT_ww(_, true);
51
+ case "day":
52
+ return FMT_dd(_, true);
53
+ case "hour":
54
+ return FMT_HH(_, true);
55
+ case "minute":
56
+ return FMT_mm(_, true);
57
+ case "second":
58
+ return FMT_ss(_, true);
29
59
  }
30
60
  return match;
31
61
  });
package/proc.d.ts CHANGED
@@ -2,7 +2,11 @@
2
2
  import sharp, { type Sharp } from "sharp";
3
3
  import { type CompLayer, type ImgProcCtx, type ImgProcOpts, type ProcSpec } from "./api.js";
4
4
  export declare const LOGGER: import("@thi.ng/logger").ProxyLogger;
5
- export declare const processImage: (src: string | Buffer | Sharp, procs: ProcSpec[], opts?: Partial<ImgProcOpts>, parentCtx?: ImgProcCtx) => Promise<sharp.Sharp>;
5
+ export declare const processImage: (src: string | Buffer | Sharp, procs: ProcSpec[], opts?: Partial<ImgProcOpts>, parentCtx?: ImgProcCtx) => Promise<{
6
+ img: sharp.Sharp;
7
+ meta: sharp.Metadata;
8
+ outputs: string[];
9
+ }>;
6
10
  /**
7
11
  * Extensible polymorphic function performing a single image processing step.
8
12
  *
package/proc.js CHANGED
@@ -51,7 +51,8 @@ const processImage = async (src, procs, opts = {}, parentCtx) => {
51
51
  ensureSize(meta);
52
52
  const ctx = {
53
53
  path: isString(src) ? src : parentCtx?.path,
54
- logger: opts?.logger || LOGGER,
54
+ outputs: parentCtx ? parentCtx.outputs : [],
55
+ logger: opts.logger || LOGGER,
55
56
  size: [meta.width, meta.height],
56
57
  channels: meta.channels,
57
58
  meta,
@@ -76,7 +77,7 @@ const processImage = async (src, procs, opts = {}, parentCtx) => {
76
77
  }
77
78
  });
78
79
  }
79
- return img;
80
+ return { img, meta, outputs: ctx.outputs };
80
81
  };
81
82
  const process = defmulti(
82
83
  (spec) => spec.type,
@@ -227,11 +228,15 @@ const process = defmulti(
227
228
  if (alpha)
228
229
  output = output.ensureAlpha();
229
230
  const { data, info } = await output.raw().toBuffer({ resolveWithObject: true });
230
- const path = join(outDir, formatPath(opts.path, data, ctx));
231
- writeFile(path, data, null, ctx.logger);
231
+ const path2 = join(
232
+ outDir,
233
+ formatPath(opts.path, ctx, spec, data)
234
+ );
235
+ writeFile(path2, data, null, ctx.logger);
236
+ ctx.outputs.push(path2);
232
237
  if (meta) {
233
238
  writeJSON(
234
- path + ".meta.json",
239
+ path2 + ".meta.json",
235
240
  info,
236
241
  void 0,
237
242
  void 0,
@@ -281,12 +286,12 @@ const process = defmulti(
281
286
  if (format)
282
287
  output = output.toFormat(format);
283
288
  const result = await output.toBuffer();
284
- writeFile(
285
- join(outDir, formatPath(opts.path, result, ctx)),
286
- result,
287
- null,
288
- ctx.logger
289
+ const path = join(
290
+ outDir,
291
+ formatPath(opts.path, ctx, spec, result)
289
292
  );
293
+ writeFile(path, result, null, ctx.logger);
294
+ ctx.outputs.push(path);
290
295
  return [input, false];
291
296
  },
292
297
  resize: async (spec, input, ctx) => {