craterview 0.3.12 → 0.3.13
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/README.md +6 -4
- package/index.ts +50 -5
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -13,9 +13,11 @@ compiles this too: any bundler, `tsx` or `ts-node`, Bun, Deno, or Node 22.6 and
|
|
|
13
13
|
type stripping (`--experimental-strip-types`, which recent releases enable by default). A
|
|
14
14
|
plain JavaScript project invoking `node` directly cannot import it.
|
|
15
15
|
|
|
16
|
-
The code itself needs Node 18 or newer, or any modern browser. **
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
The code itself needs Node 18 or newer, or any modern browser. **One runtime dependency**,
|
|
17
|
+
[`image-size`](https://www.npmjs.com/package/image-size), pure JavaScript that runs in
|
|
18
|
+
both: it reads an image's dimensions from its header — nothing is decoded — so `upload()`
|
|
19
|
+
can tell the API how big your file is and `etaSeconds` is estimated for that file rather
|
|
20
|
+
than for a typical one. `fetch`, `Blob` and `crypto.getRandomValues` are standard in both.
|
|
19
21
|
|
|
20
22
|
## Quickstart
|
|
21
23
|
|
|
@@ -150,7 +152,7 @@ Every field the API publishes on a job is exposed here.
|
|
|
150
152
|
| `error` | Set when the job failed. Safe to show a user |
|
|
151
153
|
| `errorCode` | The same fact, as a stable identifier. Branch on this, show the other |
|
|
152
154
|
| `credits` | **What you were billed** |
|
|
153
|
-
| `etaSeconds` | Seconds until the job is expected to finish, recomputed on every read — it counts down while the job runs. Absent once the job has settled |
|
|
155
|
+
| `etaSeconds` | Seconds until the job is expected to finish, recomputed on every read — it counts down while the job runs. Absent once the job has settled. Estimated for your image's size when `upload()` could read it (or when you pass `inputMegapixels` to `submit()`), for a typical image otherwise |
|
|
154
156
|
| `community` | True when the job is on the community queue: served after priority work, always taking a share of it, so it never stalls behind paid work |
|
|
155
157
|
| `outputUrl`, `downloadUrl` | The result, presigned. One to display, one to save |
|
|
156
158
|
| `thumbUrl` | A small JPEG of the result, for listings. Null when none was drawn |
|
package/index.ts
CHANGED
|
@@ -7,14 +7,18 @@
|
|
|
7
7
|
* const job = await cv.run(file, { style: "photo" });
|
|
8
8
|
* const blob = await job.blob();
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* client
|
|
10
|
+
* One dependency: `image-size`, which reads an image's dimensions from its header — no
|
|
11
|
+
* decoding — so the client can tell the API how big the file you sent is and get a wait
|
|
12
|
+
* estimated for that file rather than a typical one. fetch and Blob are standard in Node 18+
|
|
13
|
+
* and every browser, and `image-size` runs in both, so the client stays installable anywhere.
|
|
12
14
|
*/
|
|
13
15
|
|
|
16
|
+
import { imageSize } from "image-size";
|
|
17
|
+
|
|
14
18
|
// Mirrored from package.json, which is the number a release bumps. It cannot be imported
|
|
15
19
|
// from there — this ships as TypeScript, so the import would have to resolve in the
|
|
16
20
|
// consumer's toolchain — so test/version.test.ts asserts the two agree.
|
|
17
|
-
export const VERSION = "0.3.
|
|
21
|
+
export const VERSION = "0.3.13";
|
|
18
22
|
const DEFAULT_BASE_URL = "https://api.craterview.ai";
|
|
19
23
|
// The server rejects a longer wait outright, so asking for one costs a 422 rather than the
|
|
20
24
|
// wait you asked for. `run()` clamps to this rather than letting that happen.
|
|
@@ -485,6 +489,23 @@ export interface CraterViewOptions {
|
|
|
485
489
|
baseUrl?: string;
|
|
486
490
|
}
|
|
487
491
|
|
|
492
|
+
/**
|
|
493
|
+
* Width × height ÷ 10⁶ from the file's header, or `undefined` for a file the reader does not
|
|
494
|
+
* know. Reads the first megabyte only — every format's dimensions sit near the front, and a
|
|
495
|
+
* Blob of any size costs one bounded copy — and never throws: a size the client cannot read
|
|
496
|
+
* is simply not declared.
|
|
497
|
+
*/
|
|
498
|
+
async function megapixelsOf(blob: Blob): Promise<number | undefined> {
|
|
499
|
+
try {
|
|
500
|
+
const head = new Uint8Array(await blob.slice(0, 1 << 20).arrayBuffer());
|
|
501
|
+
const { width, height } = imageSize(head);
|
|
502
|
+
if (!width || !height) return undefined;
|
|
503
|
+
return Math.round((width * height) / 1e6 * 1e4) / 1e4 || undefined;
|
|
504
|
+
} catch {
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
488
509
|
export interface SubmitOptions {
|
|
489
510
|
wait?: number;
|
|
490
511
|
idempotencyKey?: string;
|
|
@@ -500,6 +521,14 @@ export interface SubmitOptions {
|
|
|
500
521
|
* becomes a model parameter, is checked against the model's schema, and would be refused.
|
|
501
522
|
*/
|
|
502
523
|
customId?: string;
|
|
524
|
+
/**
|
|
525
|
+
* The size of the file you uploaded, in megapixels (width × height ÷ 1,000,000). Used
|
|
526
|
+
* only to estimate `eta_seconds` for your image rather than for a typical one — it does
|
|
527
|
+
* not affect the price, the queue or whether the job is accepted, all of which read the
|
|
528
|
+
* file itself. Left unset, the size `upload()` read from the file is sent for a key this
|
|
529
|
+
* client uploaded; set it to override or to supply one for a key uploaded elsewhere.
|
|
530
|
+
*/
|
|
531
|
+
inputMegapixels?: number;
|
|
503
532
|
model?: string;
|
|
504
533
|
[param: string]: unknown;
|
|
505
534
|
}
|
|
@@ -507,6 +536,10 @@ export interface SubmitOptions {
|
|
|
507
536
|
export class CraterView {
|
|
508
537
|
private readonly baseUrl: string;
|
|
509
538
|
private readonly headers: Record<string, string>;
|
|
539
|
+
// What `upload()` read from each file's header, by the key it came back with, so a later
|
|
540
|
+
// `submit()` of that key declares the size without the caller carrying it. Bounded: a
|
|
541
|
+
// long-lived client uploading thousands of files keeps the most recent few hundred.
|
|
542
|
+
private readonly sizes = new Map<string, number>();
|
|
510
543
|
|
|
511
544
|
constructor(options: CraterViewOptions = {}) {
|
|
512
545
|
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
@@ -564,6 +597,11 @@ export class CraterView {
|
|
|
564
597
|
/**
|
|
565
598
|
* Put an image in storage and return its key. Bytes go straight to object storage on a
|
|
566
599
|
* presigned URL, never through the API.
|
|
600
|
+
*
|
|
601
|
+
* The image's size is read from its header on the way past and remembered against the
|
|
602
|
+
* key, so `submit()` can tell the API what you sent and `eta_seconds` is estimated for
|
|
603
|
+
* your image rather than a typical one. A file the reader does not know is uploaded just
|
|
604
|
+
* the same; only the estimate is less specific.
|
|
567
605
|
*/
|
|
568
606
|
async upload(image: Blob | ArrayBuffer | Uint8Array, contentType?: string): Promise<string> {
|
|
569
607
|
const blob = image instanceof Blob
|
|
@@ -581,6 +619,11 @@ export class CraterView {
|
|
|
581
619
|
method: "PUT", body: blob, headers: { "Content-Type": type },
|
|
582
620
|
});
|
|
583
621
|
if (!put.ok) throw new CraterViewError(`upload failed: ${put.status}`);
|
|
622
|
+
const megapixels = await megapixelsOf(blob);
|
|
623
|
+
if (megapixels) {
|
|
624
|
+
if (this.sizes.size >= 512) this.sizes.delete(this.sizes.keys().next().value!);
|
|
625
|
+
this.sizes.set(slot.input_key, megapixels);
|
|
626
|
+
}
|
|
584
627
|
return slot.input_key;
|
|
585
628
|
}
|
|
586
629
|
|
|
@@ -592,8 +635,9 @@ export class CraterView {
|
|
|
592
635
|
* job and is billed for both.
|
|
593
636
|
*/
|
|
594
637
|
async submit(inputKey: string, options: SubmitOptions = {}): Promise<Job> {
|
|
595
|
-
const { wait = 0, idempotencyKey, webhookUrl, customId,
|
|
596
|
-
...params } = options;
|
|
638
|
+
const { wait = 0, idempotencyKey, webhookUrl, customId, inputMegapixels,
|
|
639
|
+
model = "cv-enhance-v3", ...params } = options;
|
|
640
|
+
const declared = inputMegapixels ?? this.sizes.get(inputKey);
|
|
597
641
|
// Annotated, not inferred: without this the two branches unify to a type whose key
|
|
598
642
|
// may be `undefined`, which is not assignable to Record<string, string>.
|
|
599
643
|
const headers: Record<string, string> = idempotencyKey
|
|
@@ -603,6 +647,7 @@ export class CraterView {
|
|
|
603
647
|
model, input_key: inputKey, params,
|
|
604
648
|
...(webhookUrl ? { webhook_url: webhookUrl } : {}),
|
|
605
649
|
...(customId ? { custom_id: customId } : {}),
|
|
650
|
+
...(declared && declared > 0 ? { input_megapixels: declared } : {}),
|
|
606
651
|
}, headers);
|
|
607
652
|
return new Job(data);
|
|
608
653
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craterview",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"dependencies": {
|
|
3
|
+
"version": "0.3.13",
|
|
4
|
+
"dependencies": {
|
|
5
|
+
"image-size": "^2.0.4"
|
|
6
|
+
},
|
|
5
7
|
"description": "TypeScript client for the CraterView image enhancement API",
|
|
6
8
|
"type": "module",
|
|
7
9
|
"main": "index.ts",
|