@macrulez/mediatoolz 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.
- package/LICENSE +21 -0
- package/README.md +182 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +243 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/image-batch/backup.d.ts +22 -0
- package/dist/commands/image-batch/backup.js +55 -0
- package/dist/commands/image-batch/backup.js.map +1 -0
- package/dist/commands/image-batch/cache.d.ts +15 -0
- package/dist/commands/image-batch/cache.js +38 -0
- package/dist/commands/image-batch/cache.js.map +1 -0
- package/dist/commands/image-batch/cli.d.ts +15 -0
- package/dist/commands/image-batch/cli.js +323 -0
- package/dist/commands/image-batch/cli.js.map +1 -0
- package/dist/commands/image-batch/codecs.d.ts +38 -0
- package/dist/commands/image-batch/codecs.js +366 -0
- package/dist/commands/image-batch/codecs.js.map +1 -0
- package/dist/commands/image-batch/config.d.ts +121 -0
- package/dist/commands/image-batch/config.js +690 -0
- package/dist/commands/image-batch/config.js.map +1 -0
- package/dist/commands/image-batch/errors.d.ts +5 -0
- package/dist/commands/image-batch/errors.js +8 -0
- package/dist/commands/image-batch/errors.js.map +1 -0
- package/dist/commands/image-batch/geometry.d.ts +26 -0
- package/dist/commands/image-batch/geometry.js +90 -0
- package/dist/commands/image-batch/geometry.js.map +1 -0
- package/dist/commands/image-batch/inputs.d.ts +22 -0
- package/dist/commands/image-batch/inputs.js +87 -0
- package/dist/commands/image-batch/inputs.js.map +1 -0
- package/dist/commands/image-batch/manage-cli.d.ts +2 -0
- package/dist/commands/image-batch/manage-cli.js +276 -0
- package/dist/commands/image-batch/manage-cli.js.map +1 -0
- package/dist/commands/image-batch/manage.d.ts +51 -0
- package/dist/commands/image-batch/manage.js +1041 -0
- package/dist/commands/image-batch/manage.js.map +1 -0
- package/dist/commands/image-batch/overwrite.d.ts +14 -0
- package/dist/commands/image-batch/overwrite.js +31 -0
- package/dist/commands/image-batch/overwrite.js.map +1 -0
- package/dist/commands/image-batch/plan.d.ts +86 -0
- package/dist/commands/image-batch/plan.js +396 -0
- package/dist/commands/image-batch/plan.js.map +1 -0
- package/dist/commands/image-batch/process.d.ts +8 -0
- package/dist/commands/image-batch/process.js +97 -0
- package/dist/commands/image-batch/process.js.map +1 -0
- package/dist/commands/image-batch/report.d.ts +6 -0
- package/dist/commands/image-batch/report.js +251 -0
- package/dist/commands/image-batch/report.js.map +1 -0
- package/dist/commands/image-batch/restore.d.ts +29 -0
- package/dist/commands/image-batch/restore.js +125 -0
- package/dist/commands/image-batch/restore.js.map +1 -0
- package/dist/commands/image-batch/run.d.ts +127 -0
- package/dist/commands/image-batch/run.js +656 -0
- package/dist/commands/image-batch/run.js.map +1 -0
- package/dist/commands/image-batch/sharpen-ask.d.ts +6 -0
- package/dist/commands/image-batch/sharpen-ask.js +53 -0
- package/dist/commands/image-batch/sharpen-ask.js.map +1 -0
- package/dist/commands/image-batch/sharpen-manage.d.ts +26 -0
- package/dist/commands/image-batch/sharpen-manage.js +238 -0
- package/dist/commands/image-batch/sharpen-manage.js.map +1 -0
- package/dist/commands/image-batch/sharpen-presets.d.ts +24 -0
- package/dist/commands/image-batch/sharpen-presets.js +145 -0
- package/dist/commands/image-batch/sharpen-presets.js.map +1 -0
- package/dist/commands/image-batch/sharpen.d.ts +46 -0
- package/dist/commands/image-batch/sharpen.js +159 -0
- package/dist/commands/image-batch/sharpen.js.map +1 -0
- package/dist/commands/image-batch/template.d.ts +45 -0
- package/dist/commands/image-batch/template.js +142 -0
- package/dist/commands/image-batch/template.js.map +1 -0
- package/dist/commands/image-hash/cache.d.ts +14 -0
- package/dist/commands/image-hash/cache.js +40 -0
- package/dist/commands/image-hash/cache.js.map +1 -0
- package/dist/commands/image-hash/core.d.ts +61 -0
- package/dist/commands/image-hash/core.js +302 -0
- package/dist/commands/image-hash/core.js.map +1 -0
- package/dist/commands/image-hash/hash.d.ts +21 -0
- package/dist/commands/image-hash/hash.js +104 -0
- package/dist/commands/image-hash/hash.js.map +1 -0
- package/dist/commands/image-hash/report.d.ts +3 -0
- package/dist/commands/image-hash/report.js +96 -0
- package/dist/commands/image-hash/report.js.map +1 -0
- package/dist/commands/image-hash/run.d.ts +61 -0
- package/dist/commands/image-hash/run.js +317 -0
- package/dist/commands/image-hash/run.js.map +1 -0
- package/dist/commands/image-hash/sharp-loader.d.ts +64 -0
- package/dist/commands/image-hash/sharp-loader.js +81 -0
- package/dist/commands/image-hash/sharp-loader.js.map +1 -0
- package/dist/commands/ui/cli.d.ts +2 -0
- package/dist/commands/ui/cli.js +78 -0
- package/dist/commands/ui/cli.js.map +1 -0
- package/dist/commands/ui/fs-routes.d.ts +7 -0
- package/dist/commands/ui/fs-routes.js +135 -0
- package/dist/commands/ui/fs-routes.js.map +1 -0
- package/dist/commands/ui/http.d.ts +41 -0
- package/dist/commands/ui/http.js +81 -0
- package/dist/commands/ui/http.js.map +1 -0
- package/dist/commands/ui/jobs.d.ts +15 -0
- package/dist/commands/ui/jobs.js +73 -0
- package/dist/commands/ui/jobs.js.map +1 -0
- package/dist/commands/ui/modules/image-batch-backups.d.ts +2 -0
- package/dist/commands/ui/modules/image-batch-backups.js +116 -0
- package/dist/commands/ui/modules/image-batch-backups.js.map +1 -0
- package/dist/commands/ui/modules/image-batch-manage.d.ts +2 -0
- package/dist/commands/ui/modules/image-batch-manage.js +245 -0
- package/dist/commands/ui/modules/image-batch-manage.js.map +1 -0
- package/dist/commands/ui/modules/image-batch.d.ts +2 -0
- package/dist/commands/ui/modules/image-batch.js +225 -0
- package/dist/commands/ui/modules/image-batch.js.map +1 -0
- package/dist/commands/ui/modules/image-hash.d.ts +2 -0
- package/dist/commands/ui/modules/image-hash.js +158 -0
- package/dist/commands/ui/modules/image-hash.js.map +1 -0
- package/dist/commands/ui/modules/index.d.ts +2 -0
- package/dist/commands/ui/modules/index.js +4 -0
- package/dist/commands/ui/modules/index.js.map +1 -0
- package/dist/commands/ui/open-browser.d.ts +1 -0
- package/dist/commands/ui/open-browser.js +18 -0
- package/dist/commands/ui/open-browser.js.map +1 -0
- package/dist/commands/ui/scan.d.ts +20 -0
- package/dist/commands/ui/scan.js +57 -0
- package/dist/commands/ui/scan.js.map +1 -0
- package/dist/commands/ui/server.d.ts +18 -0
- package/dist/commands/ui/server.js +196 -0
- package/dist/commands/ui/server.js.map +1 -0
- package/dist/format/bytes.d.ts +2 -0
- package/dist/format/bytes.js +17 -0
- package/dist/format/bytes.js.map +1 -0
- package/dist/format/colors.d.ts +12 -0
- package/dist/format/colors.js +15 -0
- package/dist/format/colors.js.map +1 -0
- package/dist/format/diff-render.d.ts +2 -0
- package/dist/format/diff-render.js +35 -0
- package/dist/format/diff-render.js.map +1 -0
- package/dist/format/file-list.d.ts +6 -0
- package/dist/format/file-list.js +15 -0
- package/dist/format/file-list.js.map +1 -0
- package/dist/format/findings-list.d.ts +7 -0
- package/dist/format/findings-list.js +14 -0
- package/dist/format/findings-list.js.map +1 -0
- package/dist/format/style.d.ts +20 -0
- package/dist/format/style.js +36 -0
- package/dist/format/style.js.map +1 -0
- package/dist/format/table.d.ts +8 -0
- package/dist/format/table.js +44 -0
- package/dist/format/table.js.map +1 -0
- package/dist/format/vibes.d.ts +13 -0
- package/dist/format/vibes.js +82 -0
- package/dist/format/vibes.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/ui/assets/ImageBatchPage-D3f4z6YA.js +4 -0
- package/dist/ui/assets/ImageBatchPage-DwaunraE.css +1 -0
- package/dist/ui/assets/ImageHashPage-D0xZlLGw.css +1 -0
- package/dist/ui/assets/ImageHashPage-DjIlDPu-.js +1 -0
- package/dist/ui/assets/SourcePicker-D8aZjDwP.css +1 -0
- package/dist/ui/assets/SourcePicker-Db0dG6zc.js +1 -0
- package/dist/ui/assets/index-D1s-23_t.css +1 -0
- package/dist/ui/assets/index-yiehSw5D.js +3 -0
- package/dist/ui/index.html +17 -0
- package/dist/utils/closest.d.ts +2 -0
- package/dist/utils/closest.js +35 -0
- package/dist/utils/closest.js.map +1 -0
- package/dist/utils/glob.d.ts +4 -0
- package/dist/utils/glob.js +86 -0
- package/dist/utils/glob.js.map +1 -0
- package/dist/utils/split-list.d.ts +1 -0
- package/dist/utils/split-list.js +4 -0
- package/dist/utils/split-list.js.map +1 -0
- package/dist/utils/tty.d.ts +60 -0
- package/dist/utils/tty.js +390 -0
- package/dist/utils/tty.js.map +1 -0
- package/dist/utils/walk.d.ts +21 -0
- package/dist/utils/walk.js +116 -0
- package/dist/utils/walk.js.map +1 -0
- package/package.json +82 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Danil Lisin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,183 @@
|
|
|
1
|
-
#
|
|
1
|
+
# **MediaToolz**
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
A small toolbox of CLI commands for images: resize, convert and recompress
|
|
6
|
+
them in bulk by rules, and generate compact placeholders for them. One
|
|
7
|
+
binary, one subcommand per job, each safe by default (a preview first, a
|
|
8
|
+
backup before anything is overwritten).
|
|
9
|
+
|
|
10
|
+
It started as part of [devtoolz](https://www.npmjs.com/package/@macrulez/devtoolz)
|
|
11
|
+
and lives in its own package since devtoolz 0.5.0.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **`image-hash`** — generates [hazehash](https://github.com/macrulezru/hazehash),
|
|
16
|
+
[blurhash](https://blurha.sh) and/or
|
|
17
|
+
[thumbhash](https://evanw.github.io/thumbhash/) placeholders for raster
|
|
18
|
+
images (jpg, png, webp, gif, avif, tiff). Takes files and/or
|
|
19
|
+
directories — several of each, comma-separated too — and `-r` walks
|
|
20
|
+
subdirectories. Prints to stdout by default; `-o <file>` collects
|
|
21
|
+
everything into one file, `--per-file` / `--out-dir <dir>` writes a
|
|
22
|
+
file per image (`photo.jpg` → `photo.jpg.blurhash.txt`, with
|
|
23
|
+
`--suffix` and `--out-ext` to rename). Formats: `json`, `plain` (just
|
|
24
|
+
the hash text, no newline), `csv`, and `ts`/`js` modules you can import
|
|
25
|
+
straight into the app. Without `-t` it makes blurhash and thumbhash; `-t hazehash` adds the
|
|
26
|
+
more compact and more accurate hazehash (`--budget` sets its size in
|
|
27
|
+
bytes, 28 by default). Besides the hashes it can produce the
|
|
28
|
+
dominant color and a tiny PNG preview (`-t all`), accepts `http(s)`
|
|
29
|
+
URLs and path lists from a file or stdin, and can keep an output file in
|
|
30
|
+
sync: `--cache` skips unchanged images, `--update --prune` merges into
|
|
31
|
+
the existing file, `--check` fails with exit code 1 when it is stale
|
|
32
|
+
(made for CI). `--dry-run` writes nothing and shows the result as a
|
|
33
|
+
table instead. Uses the native `sharp` library, which mediatoolz
|
|
34
|
+
offers to install on first use — see Requirements.
|
|
35
|
+
- **`image-batch`** — produces resized, converted and recompressed versions
|
|
36
|
+
of raster images in bulk, by rules kept in a reusable **config** or given
|
|
37
|
+
as flags: `widths`, `heights`, `size`, `longEdge` / `shortEdge` (the same for
|
|
38
|
+
landscape and portrait), `megapixels`, `percent`, `scale` (`@2x`),
|
|
39
|
+
`matchOrientation`, `maxBytes` (`--max-size 200KB` lowers the quality as far
|
|
40
|
+
as needed), `formats` (jpg,
|
|
41
|
+
png, webp, avif, gif, tiff, heif or `original`), `fit` (`inside`,
|
|
42
|
+
`cover`, `contain`, …), codec options (`quality` as a number or a level
|
|
43
|
+
`low`/`medium`/`high`/`best`, `mozjpeg`, a png `palette`, webp `lossless`,
|
|
44
|
+
avif `effort`, …) and file names from a template
|
|
45
|
+
(`{dir}/{name}-{width}w.{format}`, with `{ext}`, `{index:3}`, `{hash}`,
|
|
46
|
+
`{scale}`, `{date}`). The config holds only the rules; the folders are
|
|
47
|
+
chosen on every run. Where the results go is one of three: `-o <dir>` — a
|
|
48
|
+
separate folder that repeats the source structure (`--flat` drops it);
|
|
49
|
+
`--beside` — next to each source; `--replace` — over the sources
|
|
50
|
+
themselves, each in its own format, to shrink oversized originals or
|
|
51
|
+
recompress them in place. `--replace` is guarded: it asks for
|
|
52
|
+
confirmation (`--yes` without a terminal), copies the originals to
|
|
53
|
+
`.image-batch-backup/<date>/` first with a `journal.json`, replaces a file
|
|
54
|
+
only if the result is smaller, never processes the same file twice with
|
|
55
|
+
the same settings, and `--dry-run` shows the real before/after sizes;
|
|
56
|
+
`image-batch restore <backup>` puts the originals back. Without a size
|
|
57
|
+
flag it only converts the format. `-r` walks
|
|
58
|
+
subfolders, `-i/--select` ticks files from an interactive list, `--list`
|
|
59
|
+
only prints them. A result an earlier run already made is skipped; any
|
|
60
|
+
other existing file is a conflict — a terminal asks once (overwrite this
|
|
61
|
+
/ this and all next / skip / skip all / quit), without a terminal it is
|
|
62
|
+
skipped with exit code 1, or use `--overwrite`, `--skip-existing`,
|
|
63
|
+
`--no-overwrite`. `--hazehash` (`--budget`), `--blurhash`, `--thumbhash`,
|
|
64
|
+
`--dominant-color` with `--emit <file>` write a manifest of everything
|
|
65
|
+
produced, with the placeholders. `init` creates configs, and `config` shows, edits,
|
|
66
|
+
copies and deletes them (`.mediatoolz/image-batch/` in the project or
|
|
67
|
+
`~/.mediatoolz/image-batch/` for all projects). `sharpen` adds output
|
|
68
|
+
sharpening after the resize — by target and amount, with `radius`,
|
|
69
|
+
`flat`, `jagged` and `threshold` for fine control — and `image-batch
|
|
70
|
+
sharpen` saves such settings as presets that configs and `--sharpen <name>`
|
|
71
|
+
use by name. Uses `sharp` too — see
|
|
72
|
+
Requirements.
|
|
73
|
+
- **`ui`** — starts a local web interface and opens it in the browser, so the
|
|
74
|
+
commands can be run with forms, tables and previews instead of flags. Two
|
|
75
|
+
modules: **Image Hash** (a gallery of the placeholders decoded next to each
|
|
76
|
+
image) and **Image Batch** (convert, edit configs and sharpening presets with
|
|
77
|
+
a before and after preview, restore backups). It is part of the package,
|
|
78
|
+
listens on `127.0.0.1` only behind a one-time token, and writes nothing
|
|
79
|
+
without an explicit choice. `--port`, `--no-open`, `--cwd`.
|
|
80
|
+
|
|
81
|
+
`image-hash` writes files only when you name an output (`-o`, `--per-file`,
|
|
82
|
+
`--out-dir`) and prints to stdout otherwise; `image-batch` writes into the
|
|
83
|
+
folder given with `-o`, next to the sources with `--beside`, and over them
|
|
84
|
+
only with `--replace` — after a confirmation, with a backup. Every command
|
|
85
|
+
supports `--json` for machine-readable output.
|
|
86
|
+
|
|
87
|
+
## Requirements
|
|
88
|
+
|
|
89
|
+
- Node.js 20+
|
|
90
|
+
- The [`sharp`](https://sharp.pixelplumbing.com) image library. It ships
|
|
91
|
+
prebuilt binaries for the common platforms and is not installed with
|
|
92
|
+
mediatoolz — the first run of either command offers to install it into
|
|
93
|
+
`~/.mediatoolz/deps` (`-y` / `--yes` agrees up front, which is also what
|
|
94
|
+
you want in CI; without a terminal and without `--yes` the command stops
|
|
95
|
+
and says so). A `sharp` you already have installed alongside mediatoolz
|
|
96
|
+
is used as-is.
|
|
97
|
+
|
|
98
|
+
## Installation
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm install -g @macrulez/mediatoolz
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Or run it without installing:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npx @macrulez/mediatoolz image-batch ./photos -r -o ./out -f webp
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Quick start
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
mediatoolz image-hash public/img # blurhash + thumbhash of every image, as JSON on stdout
|
|
114
|
+
mediatoolz image-hash public/img -r -t hazehash --budget 24 -o hashes.json # hazehash of 24 bytes at most
|
|
115
|
+
mediatoolz image-hash a.jpg,b.png,photos -r -t blurhash -f csv -o hashes.csv
|
|
116
|
+
mediatoolz image-hash public/img -r -f plain --per-file # photo.jpg.blurhash.txt + photo.jpg.thumbhash.txt
|
|
117
|
+
mediatoolz image-hash public/img -r -f ts -t thumbhash -o src/placeholders.ts --name placeholders
|
|
118
|
+
mediatoolz image-hash public/img -r --dry-run # write nothing, show a table of the hashes
|
|
119
|
+
mediatoolz image-hash public/img -r -t all --components auto -o hashes.json --cache --update --prune
|
|
120
|
+
mediatoolz image-hash public/img -r -o hashes.json --check # CI: exit 1 if hashes.json is stale
|
|
121
|
+
|
|
122
|
+
mediatoolz image-batch ./src/images -r -o ./public/images -w 400,800,1200 -f avif,webp,jpg -q high
|
|
123
|
+
mediatoolz image-batch ./photos -r -o ./out -c web # apply the saved config "web"
|
|
124
|
+
mediatoolz image-batch ./photos -r -o ./out -c web --select # tick the files from a list first
|
|
125
|
+
mediatoolz image-batch ./photos -r -o ./out -c web --dry-run # show what would be written
|
|
126
|
+
mediatoolz image-batch ./photos -r -o ./out -c web --hazehash --budget 20 --emit ./out/images.json
|
|
127
|
+
mediatoolz image-batch ./photos -r -o ./out -f webp # only change the format, keep the size
|
|
128
|
+
mediatoolz image-batch ./photos -r -o ./out --long 1600 -f webp,jpg --max-size 200KB # by long side, capped weight
|
|
129
|
+
mediatoolz image-batch ./assets -r --replace -w 1600 --dry-run # preview shrinking originals in place
|
|
130
|
+
mediatoolz image-batch ./assets -r --replace -w 1600 # do it: backup first, asks to confirm
|
|
131
|
+
mediatoolz image-batch restore .image-batch-backup/20261007-233706 # put the originals back
|
|
132
|
+
mediatoolz image-batch ./photos -r --beside -w 400 -f webp # results next to each source
|
|
133
|
+
mediatoolz image-batch init # create a config with a few questions
|
|
134
|
+
mediatoolz image-batch config # pick a config: apply, show, edit, copy, delete
|
|
135
|
+
mediatoolz image-batch sharpen new # save a sharpening preset, then --sharpen <name>
|
|
136
|
+
|
|
137
|
+
mediatoolz ui # web interface for both commands
|
|
138
|
+
mediatoolz ui --no-open --port 4477 # just print the address
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Every command has built-in `--help` — `mediatoolz --help` lists both commands
|
|
142
|
+
with their own flags inline; `mediatoolz <command> --help` for one command's
|
|
143
|
+
full flag list with defaults.
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npm install
|
|
149
|
+
npm run build # tsc -> dist/, and the web interface
|
|
150
|
+
npm test # vitest
|
|
151
|
+
npm run lint # eslint .
|
|
152
|
+
npm run format # prettier --check .
|
|
153
|
+
npm run typecheck # tsc --noEmit
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Documentation & links
|
|
159
|
+
|
|
160
|
+
- 📖 **Full documentation:** [npm.vuecraft.ru/en/packages/mediatoolz](https://npm.vuecraft.ru/en/packages/mediatoolz/guide/overview.html)
|
|
161
|
+
- 🌐 **VueCraft:** [vuecraft.ru/en](https://vuecraft.ru/en)
|
|
162
|
+
- 👤 **Author:** [macrulez.ru/en](https://macrulez.ru/en)
|
|
163
|
+
- 💻 **GitHub:** [macrulezru/mediatoolz](https://github.com/macrulezru/mediatoolz)
|
|
164
|
+
- 📦 **NPM:** [@macrulez/mediatoolz](https://www.npmjs.com/package/@macrulez/mediatoolz)
|
|
165
|
+
- 🐛 **Issues:** [github.com/macrulezru/mediatoolz/issues](https://github.com/macrulezru/mediatoolz/issues)
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 💖 Support the project
|
|
176
|
+
|
|
177
|
+
Open source takes time and effort. If this library saves you time or brings value, consider supporting further development.
|
|
178
|
+
|
|
179
|
+
<a href="https://donate.cryptocloud.plus/M6O34NIN" target="_blank">
|
|
180
|
+
<img src="https://img.shields.io/badge/Donate-CryptoCloud-8A2BE2?style=for-the-badge&logo=cryptocurrency&logoColor=white" alt="Donate via CryptoCloud">
|
|
181
|
+
</a>
|
|
182
|
+
|
|
183
|
+
Thank you for being part of this journey. ❤️
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { runImageHash } from './commands/image-hash/run.js';
|
|
6
|
+
import { renderImageHashReport } from './commands/image-hash/report.js';
|
|
7
|
+
import { DEFAULT_CACHE_FILE, DEFAULT_HAZEHASH_BUDGET, DEFAULT_IMAGE_EXTENSIONS, ImageHashUsageError, MAX_HAZEHASH_BUDGET, MIN_HAZEHASH_BUDGET, OUTPUT_FORMATS, parseBudget, parseComponents, parseExtensions, parseFileList, parseFormat, parseMaxPixels, parseSampleSize, parseTypes, } from './commands/image-hash/core.js';
|
|
8
|
+
import { registerImageBatch } from './commands/image-batch/cli.js';
|
|
9
|
+
import { registerUi } from './commands/ui/cli.js';
|
|
10
|
+
import { createStyle } from './format/style.js';
|
|
11
|
+
function wrapText(text, width) {
|
|
12
|
+
const words = text.split(' ');
|
|
13
|
+
const lines = [];
|
|
14
|
+
let current = '';
|
|
15
|
+
for (const word of words) {
|
|
16
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
17
|
+
if (current && candidate.length > width) {
|
|
18
|
+
lines.push(current);
|
|
19
|
+
current = word;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
current = candidate;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (current)
|
|
26
|
+
lines.push(current);
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Commander's own one-line-per-command "Commands:" list puts a command's
|
|
31
|
+
* own flags inline after its name (`image-hash [options]`) and leaves
|
|
32
|
+
* their descriptions for a separate `<command> --help` run — fine for a
|
|
33
|
+
* CLI with one or two flags per command, but it means the top-level
|
|
34
|
+
* `--help` never actually shows what any flag does. This builds a
|
|
35
|
+
* two-level list instead (command, then each of its own options indented
|
|
36
|
+
* below it with its own description), used in place of commander's
|
|
37
|
+
* default via `configureHelp({ visibleCommands: () => [] })` +
|
|
38
|
+
* `addHelpText('after')` — same technique as lintsync's/polyrepo-cli's
|
|
39
|
+
* own `formatCommandsHelp`.
|
|
40
|
+
*
|
|
41
|
+
* A flag present on EVERY command, with the exact same `flags` string and
|
|
42
|
+
* description (not just the same name coincidentally meaning something
|
|
43
|
+
* slightly different per command), is pulled out into a "Common options"
|
|
44
|
+
* block instead of being repeated under each one. A flag shared by only
|
|
45
|
+
* SOME commands stays listed under each of those commands directly —
|
|
46
|
+
* tried hoisting those into their own labeled sub-group too, but it's
|
|
47
|
+
* harder to keep in your head which commands a "Common options (a, b)"
|
|
48
|
+
* block actually applies to than to just see the flag where it's used.
|
|
49
|
+
* This only changes how the help text is RENDERED — every flag is still
|
|
50
|
+
* fully declared on its own command for real parsing, so
|
|
51
|
+
* `mediatoolz <command> --cwd x` keeps working exactly as before; nothing
|
|
52
|
+
* here is a true commander-level global option.
|
|
53
|
+
*/
|
|
54
|
+
function formatCommandsHelp(commands) {
|
|
55
|
+
const real = commands.filter((cmd) => cmd.name() !== 'help');
|
|
56
|
+
// Which commands (by name) share the exact same flags+description for
|
|
57
|
+
// a given option, keyed on that pair so two different flags spelled
|
|
58
|
+
// the same but described differently never get merged.
|
|
59
|
+
const owners = new Map();
|
|
60
|
+
for (const cmd of real) {
|
|
61
|
+
for (const opt of cmd.options) {
|
|
62
|
+
const key = `${opt.flags}\u0000${opt.description}`;
|
|
63
|
+
const entry = owners.get(key) ?? { flags: opt.flags, desc: opt.description, commandCount: 0 };
|
|
64
|
+
entry.commandCount++;
|
|
65
|
+
owners.set(key, entry);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const universalRows = [];
|
|
69
|
+
const hoistedKeys = new Set();
|
|
70
|
+
for (const [key, entry] of owners) {
|
|
71
|
+
if (entry.commandCount !== real.length)
|
|
72
|
+
continue; // not on every command — stays with each one below
|
|
73
|
+
universalRows.push({ indent: 1, label: entry.flags, desc: entry.desc });
|
|
74
|
+
hoistedKeys.add(key);
|
|
75
|
+
}
|
|
76
|
+
const commandGroups = real.map((cmd) => {
|
|
77
|
+
const aliases = cmd.aliases();
|
|
78
|
+
const label = aliases.length > 0 ? `${cmd.name()}, ${aliases.join(', ')}` : cmd.name();
|
|
79
|
+
const ownRows = cmd.options
|
|
80
|
+
.filter((opt) => !hoistedKeys.has(`${opt.flags}\u0000${opt.description}`))
|
|
81
|
+
.map((opt) => ({ indent: 1, label: opt.flags, desc: opt.description }));
|
|
82
|
+
return [{ indent: 0, label, desc: cmd.description() }, ...ownRows];
|
|
83
|
+
});
|
|
84
|
+
const allRows = [universalRows, ...commandGroups].flat();
|
|
85
|
+
const labelWidth = Math.max(...allRows.map((r) => r.indent * 4 + r.label.length));
|
|
86
|
+
const totalWidth = (process.stdout.isTTY && process.stdout.columns) || 96;
|
|
87
|
+
const descWidth = Math.max(totalWidth - (2 + labelWidth + 2), 30);
|
|
88
|
+
function renderRow(row, lines) {
|
|
89
|
+
const fullLabel = ' '.repeat(row.indent * 4) + row.label;
|
|
90
|
+
const [firstLine, ...restLines] = wrapText(row.desc, descWidth);
|
|
91
|
+
lines.push(` ${fullLabel.padEnd(labelWidth)} ${firstLine ?? ''}`);
|
|
92
|
+
for (const cont of restLines) {
|
|
93
|
+
lines.push(` ${' '.repeat(labelWidth)} ${cont}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const lines = [];
|
|
97
|
+
if (universalRows.length > 0) {
|
|
98
|
+
lines.push('Common options (all commands):');
|
|
99
|
+
for (const row of universalRows)
|
|
100
|
+
renderRow(row, lines);
|
|
101
|
+
lines.push('');
|
|
102
|
+
}
|
|
103
|
+
lines.push('Commands:');
|
|
104
|
+
commandGroups.forEach((group, i) => {
|
|
105
|
+
if (i > 0)
|
|
106
|
+
lines.push('');
|
|
107
|
+
for (const row of group)
|
|
108
|
+
renderRow(row, lines);
|
|
109
|
+
});
|
|
110
|
+
return lines.join('\n');
|
|
111
|
+
}
|
|
112
|
+
const program = new Command();
|
|
113
|
+
program.enablePositionalOptions();
|
|
114
|
+
program
|
|
115
|
+
.name('mediatoolz')
|
|
116
|
+
.description('A small toolbox of CLI commands for images: resize, convert and recompress them in bulk, and generate placeholders')
|
|
117
|
+
// Hide commander's own one-line-per-command list (see formatCommandsHelp
|
|
118
|
+
// above for why) — the "after" text below replaces it with the
|
|
119
|
+
// two-level version instead of showing both.
|
|
120
|
+
.configureHelp({ visibleCommands: () => [] })
|
|
121
|
+
.addHelpText('after', () => `\n${formatCommandsHelp(program.commands)}`);
|
|
122
|
+
program
|
|
123
|
+
.command('image-hash')
|
|
124
|
+
.description('Generate hazehash/blurhash/thumbhash placeholders (and dominant color, tiny preview) for raster images — to stdout, one file, or a file per image')
|
|
125
|
+
.argument('[paths...]', 'image files, directories and http(s) URLs (several allowed, comma-separated too)', [])
|
|
126
|
+
.option('--cwd <path>', 'root paths are resolved against', process.cwd())
|
|
127
|
+
.option('-r, --recursive', 'also walk subdirectories of every given directory', false)
|
|
128
|
+
.option('--files-from <file>', 'read more paths/URLs from a file, one per line (- = stdin, # starts a comment)')
|
|
129
|
+
.option('--ext <list>', 'comma-separated image extensions to pick up from directories', DEFAULT_IMAGE_EXTENSIONS.join(','))
|
|
130
|
+
.option('--ignore <glob>', 'extra ignore pattern (repeatable), on top of the built-in defaults', (val, prev) => [...prev, val], [])
|
|
131
|
+
.option('--no-respect-gitignore', "don't also honor the project's .gitignore")
|
|
132
|
+
.option('-t, --type <list>', 'what to generate, comma-separated: hazehash, blurhash, thumbhash, color (dominant), preview (tiny PNG data URI); both = blurhash+thumbhash, all = everything', 'both')
|
|
133
|
+
.option('--budget <bytes>', `hazehash: maximum size of one hash in bytes, header included, ${MIN_HAZEHASH_BUDGET}-${MAX_HAZEHASH_BUDGET} (16-48 is the tuned range)`, String(DEFAULT_HAZEHASH_BUDGET))
|
|
134
|
+
.option('--components <XxY|auto>', 'blurhash components, each side 1-9, or auto to pick them by aspect ratio', '4x3')
|
|
135
|
+
.option('--size <px>', 'longest side the image is scaled down to before hashing (1-100)', '100')
|
|
136
|
+
.option('--max-pixels <n>', 'refuse images with more pixels than this (0 = no limit; default: sharp limit, ~268 million)')
|
|
137
|
+
.option('-f, --format <format>', `format of the generated hashes: ${OUTPUT_FORMATS.join(', ')} (plain = just the hash text); --json prints the full report instead`, 'json')
|
|
138
|
+
.option('--name <identifier>', 'exported constant name for --format ts/js', 'imageHashes')
|
|
139
|
+
.option('--key-base <dir>', 'make the file keys in the output relative to this directory')
|
|
140
|
+
.option('--key-prefix <text>', 'text put in front of every file key, e.g. / for URL-style keys')
|
|
141
|
+
.option('-o, --out <file>', 'write everything into one file instead of stdout')
|
|
142
|
+
.option('--update', 'merge into the existing -o file instead of replacing it', false)
|
|
143
|
+
.option('--prune', 'with --update, drop entries whose image no longer exists', false)
|
|
144
|
+
.option('--per-file', 'write one file per image, next to the image', false)
|
|
145
|
+
.option('--out-dir <dir>', 'write the per-image files into this directory (implies --per-file)')
|
|
146
|
+
.option('--suffix <text>', 'per-image file name suffix after the image file name, {type} = hazehash/blurhash/thumbhash/hash (default: .{type})')
|
|
147
|
+
.option('--out-ext <ext>', 'per-image file extension (default: by --format, plain = .txt)')
|
|
148
|
+
.option('--check', 'write nothing; exit 1 if the output files are missing or differ from the images', false)
|
|
149
|
+
.option('--cache [file]', `skip images that did not change since the last run, remembered in this file (default: ${DEFAULT_CACHE_FILE})`)
|
|
150
|
+
.option('--concurrency <n>', 'images processed in parallel', '4')
|
|
151
|
+
.option('--dry-run', 'write nothing — show the result as a table instead', false)
|
|
152
|
+
.option('-y, --yes', 'install the missing "sharp" image library without asking', false)
|
|
153
|
+
.option('--json', 'machine-readable output', false)
|
|
154
|
+
.option('--quiet', 'suppress output when there is nothing to report', false)
|
|
155
|
+
.option('--plain', 'disable color/banner/celebration copy, even in a real terminal', false)
|
|
156
|
+
.option('--color', 'force colored output even when piped or in CI', false)
|
|
157
|
+
.action(async (paths, options) => {
|
|
158
|
+
const style = createStyle({ plain: options.plain, color: options.color });
|
|
159
|
+
const showProgress = !options.json && !options.quiet && Boolean(process.stderr.isTTY) && !options.plain;
|
|
160
|
+
let progressShown = false;
|
|
161
|
+
try {
|
|
162
|
+
const listed = options.filesFrom !== undefined
|
|
163
|
+
? parseFileList(readFileSync(options.filesFrom === '-' ? 0 : options.filesFrom, 'utf8'))
|
|
164
|
+
: [];
|
|
165
|
+
const report = await runImageHash({
|
|
166
|
+
paths: [...paths, ...listed],
|
|
167
|
+
cwd: resolve(options.cwd),
|
|
168
|
+
types: parseTypes(options.type),
|
|
169
|
+
recursive: options.recursive,
|
|
170
|
+
extensions: parseExtensions(options.ext),
|
|
171
|
+
ignoreGlobs: options.ignore,
|
|
172
|
+
respectGitignore: options.respectGitignore,
|
|
173
|
+
components: parseComponents(options.components),
|
|
174
|
+
budget: parseBudget(options.budget),
|
|
175
|
+
size: parseSampleSize(options.size),
|
|
176
|
+
...(options.maxPixels !== undefined
|
|
177
|
+
? { maxPixels: parseMaxPixels(options.maxPixels) }
|
|
178
|
+
: {}),
|
|
179
|
+
format: parseFormat(options.format),
|
|
180
|
+
exportName: options.name,
|
|
181
|
+
...(options.keyBase !== undefined ? { keyBase: options.keyBase } : {}),
|
|
182
|
+
...(options.keyPrefix !== undefined ? { keyPrefix: options.keyPrefix } : {}),
|
|
183
|
+
...(options.out !== undefined ? { out: options.out } : {}),
|
|
184
|
+
update: options.update,
|
|
185
|
+
prune: options.prune,
|
|
186
|
+
perFile: options.perFile,
|
|
187
|
+
...(options.outDir !== undefined ? { outDir: options.outDir } : {}),
|
|
188
|
+
...(options.suffix !== undefined ? { suffix: options.suffix } : {}),
|
|
189
|
+
...(options.outExt !== undefined ? { outExtension: options.outExt } : {}),
|
|
190
|
+
check: options.check,
|
|
191
|
+
...(options.cache !== undefined
|
|
192
|
+
? { cache: options.cache === true ? DEFAULT_CACHE_FILE : String(options.cache) }
|
|
193
|
+
: {}),
|
|
194
|
+
concurrency: Math.max(1, Number.parseInt(options.concurrency, 10) || 4),
|
|
195
|
+
assumeYes: options.yes,
|
|
196
|
+
dryRun: options.dryRun,
|
|
197
|
+
...(showProgress
|
|
198
|
+
? {
|
|
199
|
+
onProgress: ({ done, total }) => {
|
|
200
|
+
if (total < 2)
|
|
201
|
+
return;
|
|
202
|
+
progressShown = true;
|
|
203
|
+
process.stderr.write(`\r${style.info(`Hashing ${done}/${total}`)}`);
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
: {}),
|
|
207
|
+
});
|
|
208
|
+
if (progressShown)
|
|
209
|
+
process.stderr.write('\r\x1b[K');
|
|
210
|
+
if (options.json) {
|
|
211
|
+
console.log(JSON.stringify(report, null, 2));
|
|
212
|
+
}
|
|
213
|
+
else if (report.stdout !== null) {
|
|
214
|
+
process.stdout.write(report.stdout);
|
|
215
|
+
for (const error of report.errors)
|
|
216
|
+
console.error(`${error.file} — ${error.message}`);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const text = renderImageHashReport(report, {
|
|
220
|
+
quiet: options.quiet,
|
|
221
|
+
plain: options.plain,
|
|
222
|
+
color: options.color,
|
|
223
|
+
});
|
|
224
|
+
if (text)
|
|
225
|
+
console.log(text);
|
|
226
|
+
}
|
|
227
|
+
process.exitCode = report.exitCode;
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
if (progressShown)
|
|
231
|
+
process.stderr.write('\r\x1b[K');
|
|
232
|
+
if (error instanceof ImageHashUsageError) {
|
|
233
|
+
console.error(`error: ${error.message}`);
|
|
234
|
+
process.exitCode = 2;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
registerImageBatch(program);
|
|
241
|
+
registerUi(program);
|
|
242
|
+
program.parse(process.argv);
|
|
243
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const DEFAULT_BACKUP_DIR = ".image-batch-backup";
|
|
2
|
+
export declare const JOURNAL_FILE = "journal.json";
|
|
3
|
+
export interface JournalEntry {
|
|
4
|
+
path: string;
|
|
5
|
+
backup: string;
|
|
6
|
+
bytesBefore: number;
|
|
7
|
+
bytesAfter: number;
|
|
8
|
+
hashBefore: string;
|
|
9
|
+
hashAfter: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Journal {
|
|
12
|
+
version: 1;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
cwd: string;
|
|
15
|
+
entries: JournalEntry[];
|
|
16
|
+
}
|
|
17
|
+
export declare function backupDirFor(cwd: string, base: string | undefined, now: Date): string;
|
|
18
|
+
export declare function backupPathFor(backupDir: string, cwd: string, absolute: string): string;
|
|
19
|
+
export declare function copyIntoBackup(source: string, backup: string): void;
|
|
20
|
+
export declare function newJournal(cwd: string, now: Date): Journal;
|
|
21
|
+
export declare function saveJournal(backupDir: string, journal: Journal): void;
|
|
22
|
+
export declare function loadJournal(backupDir: string): Journal;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { ImageBatchUsageError } from './errors.js';
|
|
4
|
+
export const DEFAULT_BACKUP_DIR = '.image-batch-backup';
|
|
5
|
+
export const JOURNAL_FILE = 'journal.json';
|
|
6
|
+
function stamp(date) {
|
|
7
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
8
|
+
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
|
9
|
+
}
|
|
10
|
+
export function backupDirFor(cwd, base, now) {
|
|
11
|
+
const root = resolve(cwd, base ?? DEFAULT_BACKUP_DIR);
|
|
12
|
+
let dir = join(root, stamp(now));
|
|
13
|
+
let suffix = 1;
|
|
14
|
+
while (existsSync(dir)) {
|
|
15
|
+
suffix += 1;
|
|
16
|
+
dir = join(root, `${stamp(now)}-${suffix}`);
|
|
17
|
+
}
|
|
18
|
+
return dir;
|
|
19
|
+
}
|
|
20
|
+
export function backupPathFor(backupDir, cwd, absolute) {
|
|
21
|
+
const rel = relative(cwd, absolute);
|
|
22
|
+
if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {
|
|
23
|
+
return join(backupDir, '_outside', absolute.replace(/[:\\/]+/g, '_'));
|
|
24
|
+
}
|
|
25
|
+
return join(backupDir, rel);
|
|
26
|
+
}
|
|
27
|
+
export function copyIntoBackup(source, backup) {
|
|
28
|
+
mkdirSync(dirname(backup), { recursive: true });
|
|
29
|
+
copyFileSync(source, backup);
|
|
30
|
+
}
|
|
31
|
+
export function newJournal(cwd, now) {
|
|
32
|
+
return { version: 1, createdAt: now.toISOString(), cwd, entries: [] };
|
|
33
|
+
}
|
|
34
|
+
export function saveJournal(backupDir, journal) {
|
|
35
|
+
mkdirSync(backupDir, { recursive: true });
|
|
36
|
+
writeFileSync(join(backupDir, JOURNAL_FILE), `${JSON.stringify(journal, null, 2)}\n`);
|
|
37
|
+
}
|
|
38
|
+
export function loadJournal(backupDir) {
|
|
39
|
+
const path = join(backupDir, JOURNAL_FILE);
|
|
40
|
+
if (!existsSync(path)) {
|
|
41
|
+
throw new ImageBatchUsageError(`${backupDir} has no ${JOURNAL_FILE} — pass the dated backup folder that --replace created`);
|
|
42
|
+
}
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw new ImageBatchUsageError(`${path}: not valid JSON — ${error instanceof Error ? error.message : String(error)}`);
|
|
49
|
+
}
|
|
50
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
51
|
+
throw new ImageBatchUsageError(`${path}: not a journal written by image-batch`);
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../../../src/commands/image-batch/backup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC1F,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAElD,MAAM,CAAC,MAAM,kBAAkB,GAAG,qBAAqB,CAAA;AACvD,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAA;AAkB1C,SAAS,KAAK,CAAC,IAAU;IACvB,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACrD,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,CAAA;AAC3J,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,IAAwB,EAAE,GAAS;IAC3E,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,IAAI,kBAAkB,CAAC,CAAA;IACrD,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IAChC,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,OAAO,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,CAAC,CAAA;QACX,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC,CAAA;IAC7C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,GAAW,EAAE,QAAgB;IAC5E,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IACnC,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1D,OAAO,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAA;IACvE,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,MAAc;IAC3D,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/C,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAC9B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,GAAS;IAC/C,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;AACvE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,OAAgB;IAC7D,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACzC,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;AACvF,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAiB;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;IAC1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,oBAAoB,CAC5B,GAAG,SAAS,WAAW,YAAY,wDAAwD,CAC5F,CAAA;IACH,CAAC;IACD,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAY,CAAA;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,oBAAoB,CAC5B,GAAG,IAAI,sBAAsB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACtF,CAAA;IACH,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,oBAAoB,CAAC,GAAG,IAAI,wCAAwC,CAAC,CAAA;IACjF,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface BatchCacheEntry {
|
|
2
|
+
fingerprint: string;
|
|
3
|
+
bytes: number;
|
|
4
|
+
settingsKey?: string;
|
|
5
|
+
hash?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface BatchCache {
|
|
8
|
+
version: 1;
|
|
9
|
+
outputs: Record<string, BatchCacheEntry>;
|
|
10
|
+
}
|
|
11
|
+
export declare function emptyBatchCache(): BatchCache;
|
|
12
|
+
export declare function cacheKeyFor(placement: 'out' | 'beside' | 'replace', target: string, roots: string[]): string;
|
|
13
|
+
export declare function defaultCachePath(key: string, baseDir?: string): string;
|
|
14
|
+
export declare function loadBatchCache(path: string): BatchCache;
|
|
15
|
+
export declare function saveBatchCache(path: string, cache: BatchCache): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
export function emptyBatchCache() {
|
|
6
|
+
return { version: 1, outputs: {} };
|
|
7
|
+
}
|
|
8
|
+
export function cacheKeyFor(placement, target, roots) {
|
|
9
|
+
if (placement === 'out')
|
|
10
|
+
return resolve(target);
|
|
11
|
+
return `${placement}:${roots
|
|
12
|
+
.map((root) => resolve(root))
|
|
13
|
+
.sort()
|
|
14
|
+
.join('|')}`;
|
|
15
|
+
}
|
|
16
|
+
export function defaultCachePath(key, baseDir = homedir()) {
|
|
17
|
+
const id = createHash('sha1').update(key.toLowerCase()).digest('hex').slice(0, 16);
|
|
18
|
+
return join(baseDir, '.mediatoolz', 'cache', 'image-batch', `${id}.json`);
|
|
19
|
+
}
|
|
20
|
+
export function loadBatchCache(path) {
|
|
21
|
+
if (!existsSync(path))
|
|
22
|
+
return emptyBatchCache();
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
25
|
+
if (parsed.version !== 1 || typeof parsed.outputs !== 'object' || parsed.outputs === null) {
|
|
26
|
+
return emptyBatchCache();
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return emptyBatchCache();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function saveBatchCache(path, cache) {
|
|
35
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
36
|
+
writeFileSync(path, `${JSON.stringify(cache)}\n`);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/commands/image-batch/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAclD,MAAM,UAAU,eAAe;IAC7B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,SAAuC,EACvC,MAAc,EACd,KAAe;IAEf,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAA;IAC/C,OAAO,GAAG,SAAS,IAAI,KAAK;SACzB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;SAC5B,IAAI,EAAE;SACN,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,UAAkB,OAAO,EAAE;IACvE,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAClF,OAAO,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;AAC3E,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,eAAe,EAAE,CAAA;IAC/C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAe,CAAA;QACnE,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC1F,OAAO,eAAe,EAAE,CAAA;QAC1B,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,eAAe,EAAE,CAAA;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,KAAiB;IAC5D,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC7C,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;AACnD,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { type BatchConfig } from './config.js';
|
|
3
|
+
export declare function chooseConfig(options: {
|
|
4
|
+
config?: string;
|
|
5
|
+
cwd: string;
|
|
6
|
+
adHoc: boolean;
|
|
7
|
+
interactive: boolean;
|
|
8
|
+
}, style: {
|
|
9
|
+
plain?: boolean;
|
|
10
|
+
color?: boolean;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
config: BatchConfig;
|
|
13
|
+
label?: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function registerImageBatch(program: Command): void;
|