@azphalt/importer-cube 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/dist/cli.d.ts +2 -0
- package/dist/cli.js +73 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +51 -0
- package/dist/parse-cube.d.ts +17 -0
- package/dist/parse-cube.js +71 -0
- package/package.json +41 -0
- package/readme.md +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Az
|
|
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/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** CLI for `@azphalt/importer-cube`: wrap a `.cube` LUT into a `.azp` package. */
|
|
3
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { basename, extname } from "node:path";
|
|
5
|
+
import { importCube } from "./index.js";
|
|
6
|
+
const USAGE = "usage: azphalt-import-cube <input.cube> --id <reverse.dns.id> " +
|
|
7
|
+
"[-o out.azp] [--name N] [--version V] [--license SPDX] [--license-file PATH] [--author A]";
|
|
8
|
+
function parse(argv) {
|
|
9
|
+
const a = {};
|
|
10
|
+
for (let i = 0; i < argv.length; i++) {
|
|
11
|
+
const t = argv[i];
|
|
12
|
+
switch (t) {
|
|
13
|
+
case "-o":
|
|
14
|
+
case "--out":
|
|
15
|
+
a.out = argv[++i];
|
|
16
|
+
break;
|
|
17
|
+
case "--id":
|
|
18
|
+
a.id = argv[++i];
|
|
19
|
+
break;
|
|
20
|
+
case "--name":
|
|
21
|
+
a.name = argv[++i];
|
|
22
|
+
break;
|
|
23
|
+
case "--version":
|
|
24
|
+
a.version = argv[++i];
|
|
25
|
+
break;
|
|
26
|
+
case "--license":
|
|
27
|
+
a.license = argv[++i];
|
|
28
|
+
break;
|
|
29
|
+
case "--license-file":
|
|
30
|
+
a.licenseFile = argv[++i];
|
|
31
|
+
break;
|
|
32
|
+
case "--author":
|
|
33
|
+
a.author = argv[++i];
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
if (!t.startsWith("-") && !a.input)
|
|
37
|
+
a.input = t;
|
|
38
|
+
else
|
|
39
|
+
fail(`unknown argument: ${t}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return a;
|
|
43
|
+
}
|
|
44
|
+
function fail(msg) {
|
|
45
|
+
console.error(`error: ${msg}`);
|
|
46
|
+
console.error(USAGE);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
const args = parse(process.argv.slice(2));
|
|
50
|
+
if (!args.input)
|
|
51
|
+
fail("an input .cube file is required");
|
|
52
|
+
if (!args.id)
|
|
53
|
+
fail("--id <reverse-DNS id> is required (e.g. com.hereliesaz.teal-fade)");
|
|
54
|
+
const cubeText = readFileSync(args.input, "utf8");
|
|
55
|
+
let licenseText;
|
|
56
|
+
if (args.licenseFile) {
|
|
57
|
+
licenseText = readFileSync(args.licenseFile, "utf8");
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const spdx = args.license ?? "MIT";
|
|
61
|
+
licenseText = `${spdx}\n\nProvide the full license text with --license-file.\n`;
|
|
62
|
+
console.error(`warning: no --license-file; writing a placeholder LICENSE (SPDX: ${spdx}).`);
|
|
63
|
+
}
|
|
64
|
+
const out = args.out ?? basename(args.input, extname(args.input)) + ".azp";
|
|
65
|
+
const { azp, manifest } = importCube(cubeText, licenseText, {
|
|
66
|
+
id: args.id,
|
|
67
|
+
name: args.name,
|
|
68
|
+
version: args.version,
|
|
69
|
+
license: args.license,
|
|
70
|
+
author: args.author,
|
|
71
|
+
});
|
|
72
|
+
writeFileSync(out, azp);
|
|
73
|
+
console.error(`wrote ${out} (${manifest.id} v${manifest.version}, ${azp.length} bytes)`);
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@azphalt/importer-cube` — normalize a `.cube` LUT into a `.azp` asset package.
|
|
3
|
+
* `.cube` is already the portable color-transform interchange, so normalization is
|
|
4
|
+
* 1:1: validate/parse for metadata, store the original bytes, and wrap them in a
|
|
5
|
+
* manifest. See RATIONALE § 1.3.
|
|
6
|
+
*/
|
|
7
|
+
import { type WriteResult } from "@azphalt/azp";
|
|
8
|
+
import { type CubeLut } from "./parse-cube.js";
|
|
9
|
+
export interface ImportOptions {
|
|
10
|
+
/** Reverse-DNS id, e.g. `"com.hereliesaz.teal-fade"`. Required. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** Display name; defaults to the LUT's TITLE, then the asset stem. */
|
|
13
|
+
name?: string;
|
|
14
|
+
/** Semver; default `"1.0.0"`. */
|
|
15
|
+
version?: string;
|
|
16
|
+
/** SPDX id for the manifest; default `"MIT"`. */
|
|
17
|
+
license?: string;
|
|
18
|
+
author?: string;
|
|
19
|
+
/** Base name for the stored asset, e.g. `"teal-fade"` -> `assets/teal-fade.cube`. */
|
|
20
|
+
assetName?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ImportResult extends WriteResult {
|
|
23
|
+
/** The parsed LUT (metadata + table), for inspection. */
|
|
24
|
+
lut: CubeLut;
|
|
25
|
+
}
|
|
26
|
+
/** Parse `cubeText`, then package it as a `.azp` with `licenseText` as the LICENSE. */
|
|
27
|
+
export declare function importCube(cubeText: string, licenseText: string, opts: ImportOptions): ImportResult;
|
|
28
|
+
export { parseCube } from "./parse-cube.js";
|
|
29
|
+
export type { CubeLut } from "./parse-cube.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@azphalt/importer-cube` — normalize a `.cube` LUT into a `.azp` asset package.
|
|
3
|
+
* `.cube` is already the portable color-transform interchange, so normalization is
|
|
4
|
+
* 1:1: validate/parse for metadata, store the original bytes, and wrap them in a
|
|
5
|
+
* manifest. See RATIONALE § 1.3.
|
|
6
|
+
*/
|
|
7
|
+
import { writeAzp } from "@azphalt/azp";
|
|
8
|
+
import { parseCube } from "./parse-cube.js";
|
|
9
|
+
/** Manifest/format version this importer targets. */
|
|
10
|
+
const FORMAT = "0.1";
|
|
11
|
+
/** Parse `cubeText`, then package it as a `.azp` with `licenseText` as the LICENSE. */
|
|
12
|
+
export function importCube(cubeText, licenseText, opts) {
|
|
13
|
+
const lut = parseCube(cubeText);
|
|
14
|
+
const stem = opts.assetName ?? slug(opts.name ?? lut.title ?? "lut");
|
|
15
|
+
const assetPath = `assets/${stem}.cube`;
|
|
16
|
+
const manifest = {
|
|
17
|
+
azphalt: FORMAT,
|
|
18
|
+
id: opts.id,
|
|
19
|
+
name: opts.name ?? lut.title ?? stem,
|
|
20
|
+
version: opts.version ?? "1.0.0",
|
|
21
|
+
kind: "asset",
|
|
22
|
+
license: opts.license ?? "MIT",
|
|
23
|
+
compat: ">=0.1",
|
|
24
|
+
author: opts.author,
|
|
25
|
+
assets: [
|
|
26
|
+
{
|
|
27
|
+
type: "lut",
|
|
28
|
+
path: assetPath,
|
|
29
|
+
params: {
|
|
30
|
+
format: "cube",
|
|
31
|
+
dimensions: lut.dimensions,
|
|
32
|
+
size: lut.size,
|
|
33
|
+
domainMin: lut.domainMin,
|
|
34
|
+
domainMax: lut.domainMax,
|
|
35
|
+
...(lut.title ? { title: lut.title } : {}),
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
const result = writeAzp({
|
|
41
|
+
manifest,
|
|
42
|
+
payload: { [assetPath]: new TextEncoder().encode(cubeText) },
|
|
43
|
+
license: licenseText,
|
|
44
|
+
});
|
|
45
|
+
return { ...result, lut };
|
|
46
|
+
}
|
|
47
|
+
/** Filesystem-safe stem: lowercase, runs of non-alphanumerics to single hyphens. */
|
|
48
|
+
function slug(s) {
|
|
49
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "lut";
|
|
50
|
+
}
|
|
51
|
+
export { parseCube } from "./parse-cube.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for the Adobe/IRIDAS `.cube` LUT format (1D or 3D). Faithful enough to
|
|
3
|
+
* validate a file and extract the metadata a manifest needs; the importer stores the
|
|
4
|
+
* original bytes, so parsing is validation + description, not re-serialization.
|
|
5
|
+
*/
|
|
6
|
+
export interface CubeLut {
|
|
7
|
+
title?: string;
|
|
8
|
+
dimensions: 1 | 3;
|
|
9
|
+
/** Entries per dimension. */
|
|
10
|
+
size: number;
|
|
11
|
+
domainMin: [number, number, number];
|
|
12
|
+
domainMax: [number, number, number];
|
|
13
|
+
/** Table of `[r, g, b]` float triplets; length is `size` (1D) or `size**3` (3D). */
|
|
14
|
+
entries: [number, number, number][];
|
|
15
|
+
}
|
|
16
|
+
/** Parse `.cube` text into a {@link CubeLut}, throwing on anything malformed. */
|
|
17
|
+
export declare function parseCube(text: string): CubeLut;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for the Adobe/IRIDAS `.cube` LUT format (1D or 3D). Faithful enough to
|
|
3
|
+
* validate a file and extract the metadata a manifest needs; the importer stores the
|
|
4
|
+
* original bytes, so parsing is validation + description, not re-serialization.
|
|
5
|
+
*/
|
|
6
|
+
/** Parse `.cube` text into a {@link CubeLut}, throwing on anything malformed. */
|
|
7
|
+
export function parseCube(text) {
|
|
8
|
+
let title;
|
|
9
|
+
let dimensions;
|
|
10
|
+
let size;
|
|
11
|
+
let domainMin = [0, 0, 0];
|
|
12
|
+
let domainMax = [1, 1, 1];
|
|
13
|
+
const entries = [];
|
|
14
|
+
const lines = text.split(/\r?\n/);
|
|
15
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16
|
+
const line = lines[i].trim();
|
|
17
|
+
if (line === "" || line.startsWith("#"))
|
|
18
|
+
continue;
|
|
19
|
+
const upper = line.toUpperCase();
|
|
20
|
+
const parts = line.split(/\s+/);
|
|
21
|
+
if (upper.startsWith("TITLE")) {
|
|
22
|
+
const quoted = line.match(/"([^"]*)"/);
|
|
23
|
+
title = quoted ? quoted[1] : line.slice(5).trim();
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (upper.startsWith("LUT_1D_SIZE")) {
|
|
27
|
+
dimensions = 1;
|
|
28
|
+
size = parseInt(parts[1], 10);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (upper.startsWith("LUT_3D_SIZE")) {
|
|
32
|
+
dimensions = 3;
|
|
33
|
+
size = parseInt(parts[1], 10);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (upper.startsWith("DOMAIN_MIN")) {
|
|
37
|
+
domainMin = [num(parts[1]), num(parts[2]), num(parts[3])];
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (upper.startsWith("DOMAIN_MAX")) {
|
|
41
|
+
domainMax = [num(parts[1]), num(parts[2]), num(parts[3])];
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (upper.startsWith("LUT_1D_INPUT_RANGE") || upper.startsWith("LUT_3D_INPUT_RANGE")) {
|
|
45
|
+
const lo = num(parts[1]);
|
|
46
|
+
const hi = num(parts[2]);
|
|
47
|
+
domainMin = [lo, lo, lo];
|
|
48
|
+
domainMax = [hi, hi, hi];
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
// Otherwise: a data row of three floats.
|
|
52
|
+
if (parts.length < 3) {
|
|
53
|
+
throw new Error(`cube: malformed data on line ${i + 1}: "${lines[i]}"`);
|
|
54
|
+
}
|
|
55
|
+
entries.push([num(parts[0]), num(parts[1]), num(parts[2])]);
|
|
56
|
+
}
|
|
57
|
+
if (dimensions === undefined || size === undefined || !Number.isFinite(size) || size < 2) {
|
|
58
|
+
throw new Error("cube: missing or invalid LUT_1D_SIZE / LUT_3D_SIZE");
|
|
59
|
+
}
|
|
60
|
+
const expected = dimensions === 1 ? size : size * size * size;
|
|
61
|
+
if (entries.length !== expected) {
|
|
62
|
+
throw new Error(`cube: expected ${expected} entries for a ${dimensions}D LUT of size ${size}, got ${entries.length}`);
|
|
63
|
+
}
|
|
64
|
+
return { title, dimensions, size, domainMin, domainMax, entries };
|
|
65
|
+
}
|
|
66
|
+
function num(s) {
|
|
67
|
+
const n = Number(s);
|
|
68
|
+
if (!Number.isFinite(n))
|
|
69
|
+
throw new Error(`cube: expected a number, got "${s}"`);
|
|
70
|
+
return n;
|
|
71
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@azphalt/importer-cube",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Import .cube LUTs into .azp asset packages.",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "Az (https://hereliesaz.com)",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"bin": {
|
|
14
|
+
"azphalt-import-cube": "./dist/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@azphalt/azp": "0.1.0",
|
|
27
|
+
"@azphalt/sdk": "0.1.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"typescript": "^5.6.0",
|
|
31
|
+
"vitest": "^3.2.6",
|
|
32
|
+
"@types/node": "^22.0.0"
|
|
33
|
+
},
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
39
|
+
"clean": "rm -rf dist"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @azphalt/importer-cube
|
|
2
|
+
|
|
3
|
+
Normalize a `.cube` LUT (Adobe/IRIDAS, 1D or 3D) into a `.azp` asset package. `.cube` is already the portable color-transform interchange, so the import is 1:1: the LUT is validated and its metadata captured, the original bytes are stored verbatim, and a manifest wraps them.
|
|
4
|
+
|
|
5
|
+
## CLI
|
|
6
|
+
|
|
7
|
+
~~~sh
|
|
8
|
+
pnpm --filter @azphalt/importer-cube build
|
|
9
|
+
azphalt-import-cube teal-fade.cube --id com.hereliesaz.teal-fade -o teal-fade.azp
|
|
10
|
+
# real packaging supplies the LUT's own license text:
|
|
11
|
+
azphalt-import-cube teal-fade.cube --id com.hereliesaz.teal-fade --license-file LICENSE.txt
|
|
12
|
+
~~~
|
|
13
|
+
|
|
14
|
+
## Library
|
|
15
|
+
|
|
16
|
+
~~~ts
|
|
17
|
+
import { importCube, parseCube } from "@azphalt/importer-cube";
|
|
18
|
+
|
|
19
|
+
const { azp, manifest, lut } = importCube(cubeText, licenseText, { id: "com.hereliesaz.teal-fade" });
|
|
20
|
+
~~~
|