@giveitsmaller/sdk 0.10.0 → 0.12.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/dist/builder.d.ts +14 -0
- package/dist/builder.js +56 -0
- package/dist/ergonomic/preset_resolver.d.ts +6 -0
- package/dist/ergonomic/preset_resolver.js +10 -1
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +16 -0
- package/dist/file-first.d.ts +24 -15
- package/dist/file-first.js +98 -33
- package/dist/generated/sdk_spec/presets.js +0 -9
- package/dist/generated/sdk_spec/version.d.ts +1 -1
- package/dist/generated/sdk_spec/version.js +1 -1
- package/dist/index.core.d.ts +1 -1
- package/dist/index.core.js +3 -0
- package/package.json +2 -2
package/dist/builder.d.ts
CHANGED
|
@@ -41,6 +41,20 @@ import type { PresetDefaults, PresetMedia } from './ergonomic/presets/index.js';
|
|
|
41
41
|
* @internal — exported for tests + the preset resolver.
|
|
42
42
|
*/
|
|
43
43
|
export declare function _detectCompressMedia(input: string | Blob): PresetMedia | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Best-effort classification of whether an audio input is LOSSLESS
|
|
46
|
+
* (flac/wav) vs lossy. The worker rejects `bitrate` on lossless audio
|
|
47
|
+
* (compress.yaml / contracts iakhSy3E), so the preset resolver uses this
|
|
48
|
+
* to drop the shipped-preset bitrate for clear-cut lossless inputs.
|
|
49
|
+
*
|
|
50
|
+
* Detection is filename/MIME only — it CANNOT probe the actual codec, so
|
|
51
|
+
* any ambiguous or unknown input classifies as lossy (keep bitrate). The
|
|
52
|
+
* worker stays authoritative: a user-supplied bitrate on a lossless file
|
|
53
|
+
* still reaches the wire and earns a deliberate 422.
|
|
54
|
+
*
|
|
55
|
+
* @internal — exported for tests + the preset resolver.
|
|
56
|
+
*/
|
|
57
|
+
export declare function _detectAudioLossless(input: string | Blob): boolean;
|
|
44
58
|
/**
|
|
45
59
|
* Lightweight artifact reference passed to a `.mapEach(...)` fn. Mirrors
|
|
46
60
|
* the subset of `Artifact` a fan-out callback can use to construct the
|
package/dist/builder.js
CHANGED
|
@@ -103,6 +103,59 @@ export function _detectCompressMedia(input) {
|
|
|
103
103
|
return 'document_office';
|
|
104
104
|
return undefined;
|
|
105
105
|
}
|
|
106
|
+
// LOSSLESS audio per the compress.yaml contract (contracts iakhSy3E) —
|
|
107
|
+
// flac/wav ONLY. Everything else audio (mp3/mpeg, aac, ogg, m4a/mp4, opus)
|
|
108
|
+
// and any unknown input is treated as lossy so the shipped-preset bitrate is
|
|
109
|
+
// kept. `aiff` is deliberately absent — not a contract audio format.
|
|
110
|
+
const LOSSLESS_AUDIO_MIMES = new Set([
|
|
111
|
+
'audio/flac',
|
|
112
|
+
'audio/x-flac',
|
|
113
|
+
'audio/wav',
|
|
114
|
+
'audio/x-wav',
|
|
115
|
+
'audio/wave',
|
|
116
|
+
]);
|
|
117
|
+
const LOSSLESS_AUDIO_EXTENSIONS = new Set(['flac', 'wav']);
|
|
118
|
+
/**
|
|
119
|
+
* Best-effort classification of whether an audio input is LOSSLESS
|
|
120
|
+
* (flac/wav) vs lossy. The worker rejects `bitrate` on lossless audio
|
|
121
|
+
* (compress.yaml / contracts iakhSy3E), so the preset resolver uses this
|
|
122
|
+
* to drop the shipped-preset bitrate for clear-cut lossless inputs.
|
|
123
|
+
*
|
|
124
|
+
* Detection is filename/MIME only — it CANNOT probe the actual codec, so
|
|
125
|
+
* any ambiguous or unknown input classifies as lossy (keep bitrate). The
|
|
126
|
+
* worker stays authoritative: a user-supplied bitrate on a lossless file
|
|
127
|
+
* still reaches the wire and earns a deliberate 422.
|
|
128
|
+
*
|
|
129
|
+
* @internal — exported for tests + the preset resolver.
|
|
130
|
+
*/
|
|
131
|
+
export function _detectAudioLossless(input) {
|
|
132
|
+
let filename;
|
|
133
|
+
let mime;
|
|
134
|
+
if (typeof input === 'string') {
|
|
135
|
+
filename = input;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
mime = input.type !== '' ? input.type : undefined;
|
|
139
|
+
const named = input.name;
|
|
140
|
+
if (typeof named === 'string')
|
|
141
|
+
filename = named;
|
|
142
|
+
}
|
|
143
|
+
// MIME-first if a recognised audio MIME is present — Blob.type is canonical.
|
|
144
|
+
// Strip any MIME parameters (`audio/flac; codecs=flac`) before the exact-set
|
|
145
|
+
// lookup so a parameterised type still classifies as lossless — `media` is
|
|
146
|
+
// already `audio` via the prefix check, so a miss would wrongly keep the
|
|
147
|
+
// bitrate (codex 18b6b684).
|
|
148
|
+
if (mime !== undefined && mime.startsWith('audio/')) {
|
|
149
|
+
const bareMime = mime.split(';')[0].trim().toLowerCase();
|
|
150
|
+
return LOSSLESS_AUDIO_MIMES.has(bareMime);
|
|
151
|
+
}
|
|
152
|
+
if (filename === undefined)
|
|
153
|
+
return false;
|
|
154
|
+
const ext = filename.toLowerCase().split('.').pop();
|
|
155
|
+
if (ext === undefined)
|
|
156
|
+
return false;
|
|
157
|
+
return LOSSLESS_AUDIO_EXTENSIONS.has(ext);
|
|
158
|
+
}
|
|
106
159
|
// ---------------------------------------------------------------------------
|
|
107
160
|
// OperationBuilder
|
|
108
161
|
// ---------------------------------------------------------------------------
|
|
@@ -177,6 +230,9 @@ export class OperationBuilder {
|
|
|
177
230
|
op: 'compress',
|
|
178
231
|
explicitOptions,
|
|
179
232
|
};
|
|
233
|
+
if (media === 'audio') {
|
|
234
|
+
input.audioLossless = _detectAudioLossless(this.input);
|
|
235
|
+
}
|
|
180
236
|
if (this.presetDefaults !== undefined) {
|
|
181
237
|
input.presetDefaults = this.presetDefaults;
|
|
182
238
|
}
|
|
@@ -36,6 +36,12 @@ export interface ResolveCompressOptionsInput {
|
|
|
36
36
|
* layer.
|
|
37
37
|
*/
|
|
38
38
|
readonly explicitOptions: Readonly<Record<string, unknown>>;
|
|
39
|
+
/**
|
|
40
|
+
* Classifier result from media detection. When `true` on audio, the
|
|
41
|
+
* shipped-preset (sdkDefault) bitrate is dropped — the worker rejects
|
|
42
|
+
* `bitrate` on lossless outputs (flac/wav — contracts iakhSy3E).
|
|
43
|
+
*/
|
|
44
|
+
readonly audioLossless?: boolean;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
47
|
* Output of {@link resolveCompressOptions}. `wireOptions` is the
|
|
@@ -432,7 +432,7 @@ function computePresetConfigHash(clientDefault, scopedDefault, callPresetOverrid
|
|
|
432
432
|
* `optimize` unset ⇒ layer 1 contributes nothing; `resolvedOptions.preset = null`.
|
|
433
433
|
*/
|
|
434
434
|
export function resolveCompressOptions(input) {
|
|
435
|
-
const { media, op, presetDefaults, scopedPresetDefaults, presetOverrides, optimize, explicitOptions } = input;
|
|
435
|
+
const { media, op, presetDefaults, scopedPresetDefaults, presetOverrides, optimize, explicitOptions, audioLossless } = input;
|
|
436
436
|
if (op !== 'compress') {
|
|
437
437
|
throw new GislConfigError(`Preset resolution is only wired for compress operations today; got op='${op}'.`, { reason: 'unsupported_op' });
|
|
438
438
|
}
|
|
@@ -510,6 +510,15 @@ export function resolveCompressOptions(input) {
|
|
|
510
510
|
acc.winners.set('encoding_mode', crfSource);
|
|
511
511
|
}
|
|
512
512
|
}
|
|
513
|
+
// audio_compress bakes a bitrate (Size 96 / Balanced 192 / Quality 320); the
|
|
514
|
+
// worker rejects `bitrate` on lossless outputs (flac/wav — contracts iakhSy3E).
|
|
515
|
+
// Drop ONLY the shipped-preset (sdkDefault) bitrate for clear-cut lossless
|
|
516
|
+
// audio; any user-supplied bitrate (client/scoped default, per-call override
|
|
517
|
+
// or explicit) is left for the worker to reject — no silent-ignore.
|
|
518
|
+
if (media === 'audio' && audioLossless === true && acc.winners.get('bitrate') === 'sdkDefault') {
|
|
519
|
+
delete acc.merged.bitrate;
|
|
520
|
+
acc.winners.delete('bitrate');
|
|
521
|
+
}
|
|
513
522
|
// 7. Validate the merged payload (post-merge — catches cross-layer
|
|
514
523
|
// disagreements). May throw GislConfigError with resolvedSnapshot.
|
|
515
524
|
const explicitWireKeys = new Set();
|
package/dist/errors.d.ts
CHANGED
|
@@ -342,6 +342,18 @@ export declare class GislChainCardinalityMismatchError extends GislConfigError {
|
|
|
342
342
|
readonly attemptedOperation: string;
|
|
343
343
|
constructor(previousOperation: string, attemptedOperation: string);
|
|
344
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Thrown by `.bundle(...)` when the target builder's terminal job is already an
|
|
347
|
+
* `archive` op — double-bundle prevention (a builder that already produces an
|
|
348
|
+
* archive cannot be bundled again). No HTTP: raised during lowering, before any
|
|
349
|
+
* upload. Per the lowering spec
|
|
350
|
+
* (`docs/plans/sdk-ergonomics/lowering.md:484`, id `bundle_already_archived_error`).
|
|
351
|
+
* Dormant until `.bundle()` ships (wpHoJhuo) — the type lands here so that PR is
|
|
352
|
+
* a pure addition.
|
|
353
|
+
*/
|
|
354
|
+
export declare class GislBundleAlreadyArchivedError extends GislConfigError {
|
|
355
|
+
constructor();
|
|
356
|
+
}
|
|
345
357
|
export declare class GislTimeoutError extends GislError {
|
|
346
358
|
constructor(message: string);
|
|
347
359
|
}
|
package/dist/errors.js
CHANGED
|
@@ -349,6 +349,22 @@ export class GislChainCardinalityMismatchError extends GislConfigError {
|
|
|
349
349
|
this.attemptedOperation = attemptedOperation;
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Thrown by `.bundle(...)` when the target builder's terminal job is already an
|
|
354
|
+
* `archive` op — double-bundle prevention (a builder that already produces an
|
|
355
|
+
* archive cannot be bundled again). No HTTP: raised during lowering, before any
|
|
356
|
+
* upload. Per the lowering spec
|
|
357
|
+
* (`docs/plans/sdk-ergonomics/lowering.md:484`, id `bundle_already_archived_error`).
|
|
358
|
+
* Dormant until `.bundle()` ships (wpHoJhuo) — the type lands here so that PR is
|
|
359
|
+
* a pure addition.
|
|
360
|
+
*/
|
|
361
|
+
export class GislBundleAlreadyArchivedError extends GislConfigError {
|
|
362
|
+
constructor() {
|
|
363
|
+
super('This builder already produces an archive (bundle); .bundle() cannot be ' +
|
|
364
|
+
'applied to an already-bundled builder.');
|
|
365
|
+
this.name = 'GislBundleAlreadyArchivedError';
|
|
366
|
+
}
|
|
367
|
+
}
|
|
352
368
|
export class GislTimeoutError extends GislError {
|
|
353
369
|
constructor(message) {
|
|
354
370
|
super(message);
|
package/dist/file-first.d.ts
CHANGED
|
@@ -329,23 +329,31 @@ export declare class Recipe {
|
|
|
329
329
|
/**
|
|
330
330
|
* Reduce file size. `optimize` selects a per-media preset (resolved to
|
|
331
331
|
* concrete wire fields at lower-time, exactly as `client.compress()` does).
|
|
332
|
+
* `options` carries the full per-op options bag (mirrors
|
|
333
|
+
* `client.compress(input, options)`); the explicit `optimize` param wins
|
|
334
|
+
* over any `optimize` key in the bag.
|
|
332
335
|
*/
|
|
333
|
-
compress(optimize?: OptimizeFor): Recipe;
|
|
334
|
-
/** Change format. `format` is lowered verbatim to the `format` wire option. */
|
|
335
|
-
convert(format: string): Recipe;
|
|
336
|
+
compress(optimize?: OptimizeFor, options?: Record<string, unknown>): Recipe;
|
|
336
337
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
338
|
+
* Change format. `format` is lowered verbatim to the `format` wire option;
|
|
339
|
+
* `options` carries any additional per-op convert options.
|
|
340
|
+
*/
|
|
341
|
+
convert(format: string, options?: Record<string, unknown>): Recipe;
|
|
342
|
+
/**
|
|
343
|
+
* Generate a preview. Width and/or height in pixels; any additional per-op
|
|
344
|
+
* thumbnail options pass through. An omitted (`undefined`) value is dropped
|
|
345
|
+
* from the wire options (not sent as `undefined`).
|
|
339
346
|
*/
|
|
340
347
|
thumbnail(options?: {
|
|
341
348
|
width?: number;
|
|
342
349
|
height?: number;
|
|
343
|
-
}): Recipe;
|
|
350
|
+
} & Record<string, unknown>): Recipe;
|
|
344
351
|
/**
|
|
345
352
|
* Apply a text watermark. Single-input (the text is an option, not a
|
|
346
|
-
* secondary file) — lowers to the `text_watermark` op with a `text` option
|
|
353
|
+
* secondary file) — lowers to the `text_watermark` op with a `text` option;
|
|
354
|
+
* `options` carries any additional per-op watermark options.
|
|
347
355
|
*/
|
|
348
|
-
textWatermark(text: string): Recipe;
|
|
356
|
+
textWatermark(text: string, options?: Record<string, unknown>): Recipe;
|
|
349
357
|
/**
|
|
350
358
|
* Lower this recipe to a workflow-create payload against a resolved upload
|
|
351
359
|
* id. Single-input chain → ONE job, `source: upload(fileId)`, ordered
|
|
@@ -417,6 +425,7 @@ export declare class Recipe {
|
|
|
417
425
|
private lowerStep;
|
|
418
426
|
private lowerCompressOptions;
|
|
419
427
|
private compressMediaHint;
|
|
428
|
+
private compressAudioLossless;
|
|
420
429
|
}
|
|
421
430
|
/**
|
|
422
431
|
* The homogeneous fan-out builder value (FF3a). `client.files([a, b, c])`
|
|
@@ -455,16 +464,16 @@ export declare class FilesRecipe {
|
|
|
455
464
|
* preset). Reuses {@link Recipe}'s validation — a directly-constructed
|
|
456
465
|
* lowering builds an internal Recipe that throws the same `GislConfigError`.
|
|
457
466
|
*/
|
|
458
|
-
compress(optimize?: OptimizeFor): FilesRecipe;
|
|
467
|
+
compress(optimize?: OptimizeFor, options?: Record<string, unknown>): FilesRecipe;
|
|
459
468
|
/** Change every input's format. `format` lowers verbatim to the `format` option. */
|
|
460
|
-
convert(format: string): FilesRecipe;
|
|
469
|
+
convert(format: string, options?: Record<string, unknown>): FilesRecipe;
|
|
461
470
|
/** Generate a preview of every input. Omitted dimensions are dropped from the wire options. */
|
|
462
471
|
thumbnail(options?: {
|
|
463
472
|
width?: number;
|
|
464
473
|
height?: number;
|
|
465
|
-
}): FilesRecipe;
|
|
474
|
+
} & Record<string, unknown>): FilesRecipe;
|
|
466
475
|
/** Apply the same text watermark to every input. */
|
|
467
|
-
textWatermark(text: string): FilesRecipe;
|
|
476
|
+
textWatermark(text: string, options?: Record<string, unknown>): FilesRecipe;
|
|
468
477
|
/**
|
|
469
478
|
* Combine the inputs into ONE output (N→1), in array order (FF3b). Returns a
|
|
470
479
|
* single-output {@link MergedRecipe} you chain further ops on
|
|
@@ -587,14 +596,14 @@ export declare class MergedRecipe {
|
|
|
587
596
|
private readonly client?;
|
|
588
597
|
constructor(inputs: readonly FileInput[], mergeOptions: MergeOptions, postSteps?: readonly RecipeStep[], presetDefaults?: PresetDefaults | undefined, scopedPresetDefaults?: PresetDefaults | undefined, client?: GislClient | undefined);
|
|
589
598
|
/** Reduce the merged output's size. See {@link Recipe.compress}. */
|
|
590
|
-
compress(optimize?: OptimizeFor): MergedRecipe;
|
|
599
|
+
compress(optimize?: OptimizeFor, options?: Record<string, unknown>): MergedRecipe;
|
|
591
600
|
/** Change the merged output's format. See {@link Recipe.convert}. */
|
|
592
|
-
convert(format: string): MergedRecipe;
|
|
601
|
+
convert(format: string, options?: Record<string, unknown>): MergedRecipe;
|
|
593
602
|
/** Thumbnail the merged output. Omitted dimensions are dropped from the wire options. */
|
|
594
603
|
thumbnail(options?: {
|
|
595
604
|
width?: number;
|
|
596
605
|
height?: number;
|
|
597
|
-
}): MergedRecipe;
|
|
606
|
+
} & Record<string, unknown>): MergedRecipe;
|
|
598
607
|
/**
|
|
599
608
|
* Lower to the merge DAG: one `passthrough` source job per input + one
|
|
600
609
|
* `merge` job whose `operations[]` is `[merge, ...post-combine ops]`. The
|
package/dist/file-first.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Mirrors `packages/php/src/FileFirst/*`.
|
|
11
11
|
*/
|
|
12
12
|
import { GislConfigError, GislNetworkError, GislNoSuchKeyError, GislSinkError, GislTimeoutError, SseEndedWithoutTerminal } from './errors.js';
|
|
13
|
-
import { _detectCompressMedia, _consumeSseToTerminal, _pollToTerminal, _parseMaxWait, _checkAborted, } from './builder.js';
|
|
13
|
+
import { _detectCompressMedia, _detectAudioLossless, _consumeSseToTerminal, _pollToTerminal, _parseMaxWait, _checkAborted, } from './builder.js';
|
|
14
14
|
import { LazyHttpDownloader } from './lazy-downloader.js';
|
|
15
15
|
import { resolveCompressOptions, } from './ergonomic/preset_resolver.js';
|
|
16
16
|
import { OptimizeFor } from './generated/sdk_spec/enums.js';
|
|
@@ -421,36 +421,50 @@ export class Recipe {
|
|
|
421
421
|
/**
|
|
422
422
|
* Reduce file size. `optimize` selects a per-media preset (resolved to
|
|
423
423
|
* concrete wire fields at lower-time, exactly as `client.compress()` does).
|
|
424
|
+
* `options` carries the full per-op options bag (mirrors
|
|
425
|
+
* `client.compress(input, options)`); the explicit `optimize` param wins
|
|
426
|
+
* over any `optimize` key in the bag.
|
|
424
427
|
*/
|
|
425
|
-
compress(optimize) {
|
|
428
|
+
compress(optimize, options = {}) {
|
|
426
429
|
if (optimize !== undefined && !Object.values(OptimizeFor).includes(optimize)) {
|
|
427
430
|
const allowed = Object.values(OptimizeFor).join(', ');
|
|
428
431
|
throw new GislConfigError(`compress 'optimize' must be one of ${allowed}; got '${String(optimize)}'.`, { reason: 'invalid_optimize', conflictingFields: ['optimize'] });
|
|
429
432
|
}
|
|
430
|
-
return this.withStep({
|
|
433
|
+
return this.withStep({
|
|
434
|
+
opType: 'compress',
|
|
435
|
+
options: { ...options, ...(optimize !== undefined ? { optimize } : {}) },
|
|
436
|
+
});
|
|
431
437
|
}
|
|
432
|
-
/**
|
|
433
|
-
|
|
434
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Change format. `format` is lowered verbatim to the `format` wire option;
|
|
440
|
+
* `options` carries any additional per-op convert options.
|
|
441
|
+
*/
|
|
442
|
+
convert(format, options = {}) {
|
|
443
|
+
// Spread options FIRST so the explicit `format` argument is authoritative —
|
|
444
|
+
// a `format` key in the bag must NOT silently override the call's format.
|
|
445
|
+
return this.withStep({ opType: 'convert', options: { ...options, format } });
|
|
435
446
|
}
|
|
436
447
|
/**
|
|
437
|
-
* Generate a preview. Width and/or height in pixels;
|
|
438
|
-
*
|
|
448
|
+
* Generate a preview. Width and/or height in pixels; any additional per-op
|
|
449
|
+
* thumbnail options pass through. An omitted (`undefined`) value is dropped
|
|
450
|
+
* from the wire options (not sent as `undefined`).
|
|
439
451
|
*/
|
|
440
452
|
thumbnail(options = {}) {
|
|
441
453
|
const wire = {};
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
454
|
+
for (const [key, value] of Object.entries(options)) {
|
|
455
|
+
if (value !== undefined)
|
|
456
|
+
wire[key] = value;
|
|
457
|
+
}
|
|
446
458
|
return this.withStep({ opType: 'thumbnail', options: wire });
|
|
447
459
|
}
|
|
448
460
|
/**
|
|
449
461
|
* Apply a text watermark. Single-input (the text is an option, not a
|
|
450
|
-
* secondary file) — lowers to the `text_watermark` op with a `text` option
|
|
462
|
+
* secondary file) — lowers to the `text_watermark` op with a `text` option;
|
|
463
|
+
* `options` carries any additional per-op watermark options.
|
|
451
464
|
*/
|
|
452
|
-
textWatermark(text) {
|
|
453
|
-
|
|
465
|
+
textWatermark(text, options = {}) {
|
|
466
|
+
// Spread options FIRST so the explicit `text` argument is authoritative.
|
|
467
|
+
return this.withStep({ opType: 'text_watermark', options: { ...options, text } });
|
|
454
468
|
}
|
|
455
469
|
/**
|
|
456
470
|
* Lower this recipe to a workflow-create payload against a resolved upload
|
|
@@ -636,21 +650,55 @@ export class Recipe {
|
|
|
636
650
|
? { type: step.opType }
|
|
637
651
|
: { type: step.opType, options };
|
|
638
652
|
}
|
|
639
|
-
lowerCompressOptions(
|
|
640
|
-
|
|
653
|
+
lowerCompressOptions(stepOptions) {
|
|
654
|
+
// Mirror the op-first resolver precedence (OperationBuilder._resolve in
|
|
655
|
+
// builder.ts): optimize = preset layer, presetOverrides = callPresetOverride
|
|
656
|
+
// layer, the rest = explicit layer.
|
|
657
|
+
const { optimize, presetOverrides, ...explicitOptions } = stepOptions;
|
|
658
|
+
// Validate the special bag keys at this chokepoint (every compress lowers
|
|
659
|
+
// through here). The chain methods' shorthand-param guard only covers a
|
|
660
|
+
// PARAM-supplied optimize; a bag-supplied optimize / presetOverrides must be
|
|
661
|
+
// validated too, so a bad value raises the typed SDK error rather than
|
|
662
|
+
// surfacing as a raw preset-lookup error or TypeError downstream. Mirrors
|
|
663
|
+
// PHP coerceOptimize + OperationBuilder::normalisePresetOverrides.
|
|
664
|
+
if (optimize !== undefined && !Object.values(OptimizeFor).includes(optimize)) {
|
|
665
|
+
const allowed = Object.values(OptimizeFor).join(', ');
|
|
666
|
+
throw new GislConfigError(`compress 'optimize' must be one of ${allowed}; got '${String(optimize)}'.`, { reason: 'invalid_optimize', conflictingFields: ['optimize'] });
|
|
667
|
+
}
|
|
668
|
+
if (presetOverrides !== undefined &&
|
|
669
|
+
(presetOverrides === null || typeof presetOverrides !== 'object' || Array.isArray(presetOverrides))) {
|
|
670
|
+
const got = Array.isArray(presetOverrides)
|
|
671
|
+
? 'array'
|
|
672
|
+
: presetOverrides === null
|
|
673
|
+
? 'null'
|
|
674
|
+
: typeof presetOverrides;
|
|
675
|
+
throw new GislConfigError(`compress 'presetOverrides' must be a *CompressPresetOptions object; got ${got}.`, { reason: 'invalid_preset_overrides', conflictingFields: ['presetOverrides'] });
|
|
676
|
+
}
|
|
641
677
|
const media = this.compressMediaHint();
|
|
642
678
|
if (media === undefined) {
|
|
643
679
|
// Cannot infer a media class (a Blob without a recognised name, or a
|
|
644
680
|
// bare upload id) → preset resolution is impossible. Fail FAST rather
|
|
645
681
|
// than silently dropping an explicit `optimize`; bare compress() is fine.
|
|
682
|
+
// When no optimize is set, pass any explicit options through verbatim
|
|
683
|
+
// (exactly as op-first `_resolve` does when media is undefined).
|
|
646
684
|
if (optimize !== undefined) {
|
|
647
685
|
throw new GislConfigError(`compress(optimize: ${String(optimize)}) needs a media type to resolve the preset, but the ` +
|
|
648
686
|
'input has no inferable media (a pre-uploaded file id or unnamed Blob carries no extension). ' +
|
|
649
687
|
'Use a path with a file extension, or call compress() without optimize.', { reason: 'media_unknown', conflictingFields: ['optimize'] });
|
|
650
688
|
}
|
|
651
|
-
|
|
689
|
+
// presetOverrides override a resolved preset; with no media there is no
|
|
690
|
+
// preset to override, so fail fast rather than silently dropping them.
|
|
691
|
+
if (presetOverrides !== undefined) {
|
|
692
|
+
throw new GislConfigError('compress(presetOverrides) needs a media type to resolve the preset to override, but the ' +
|
|
693
|
+
'input has no inferable media (a pre-uploaded file id or unnamed Blob carries no extension). ' +
|
|
694
|
+
'Use a path with a file extension.', { reason: 'media_unknown', conflictingFields: ['presetOverrides'] });
|
|
695
|
+
}
|
|
696
|
+
return { ...explicitOptions };
|
|
697
|
+
}
|
|
698
|
+
const input = { media, op: 'compress', explicitOptions };
|
|
699
|
+
if (media === 'audio') {
|
|
700
|
+
input.audioLossless = this.compressAudioLossless();
|
|
652
701
|
}
|
|
653
|
-
const input = { media, op: 'compress', explicitOptions: {} };
|
|
654
702
|
if (this.presetDefaults !== undefined) {
|
|
655
703
|
input.presetDefaults = this.presetDefaults;
|
|
656
704
|
}
|
|
@@ -658,6 +706,11 @@ export class Recipe {
|
|
|
658
706
|
input.scopedPresetDefaults =
|
|
659
707
|
this.scopedPresetDefaults;
|
|
660
708
|
}
|
|
709
|
+
if (presetOverrides !== undefined) {
|
|
710
|
+
// Validated above to be a non-null, non-array object.
|
|
711
|
+
input.presetOverrides =
|
|
712
|
+
presetOverrides;
|
|
713
|
+
}
|
|
661
714
|
if (optimize !== undefined) {
|
|
662
715
|
input.optimize = optimize;
|
|
663
716
|
}
|
|
@@ -672,6 +725,13 @@ export class Recipe {
|
|
|
672
725
|
}
|
|
673
726
|
return undefined;
|
|
674
727
|
}
|
|
728
|
+
compressAudioLossless() {
|
|
729
|
+
if (this.input.kind === 'path')
|
|
730
|
+
return _detectAudioLossless(this.input.path);
|
|
731
|
+
if (this.input.kind === 'blob')
|
|
732
|
+
return _detectAudioLossless(this.input.blob);
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
675
735
|
}
|
|
676
736
|
/**
|
|
677
737
|
* The homogeneous fan-out builder value (FF3a). `client.files([a, b, c])`
|
|
@@ -716,20 +776,20 @@ export class FilesRecipe {
|
|
|
716
776
|
* preset). Reuses {@link Recipe}'s validation — a directly-constructed
|
|
717
777
|
* lowering builds an internal Recipe that throws the same `GislConfigError`.
|
|
718
778
|
*/
|
|
719
|
-
compress(optimize) {
|
|
720
|
-
return this.withStep(this.baseRecipe().compress(optimize));
|
|
779
|
+
compress(optimize, options = {}) {
|
|
780
|
+
return this.withStep(this.baseRecipe().compress(optimize, options));
|
|
721
781
|
}
|
|
722
782
|
/** Change every input's format. `format` lowers verbatim to the `format` option. */
|
|
723
|
-
convert(format) {
|
|
724
|
-
return this.withStep(this.baseRecipe().convert(format));
|
|
783
|
+
convert(format, options = {}) {
|
|
784
|
+
return this.withStep(this.baseRecipe().convert(format, options));
|
|
725
785
|
}
|
|
726
786
|
/** Generate a preview of every input. Omitted dimensions are dropped from the wire options. */
|
|
727
787
|
thumbnail(options = {}) {
|
|
728
788
|
return this.withStep(this.baseRecipe().thumbnail(options));
|
|
729
789
|
}
|
|
730
790
|
/** Apply the same text watermark to every input. */
|
|
731
|
-
textWatermark(text) {
|
|
732
|
-
return this.withStep(this.baseRecipe().textWatermark(text));
|
|
791
|
+
textWatermark(text, options = {}) {
|
|
792
|
+
return this.withStep(this.baseRecipe().textWatermark(text, options));
|
|
733
793
|
}
|
|
734
794
|
/**
|
|
735
795
|
* Combine the inputs into ONE output (N→1), in array order (FF3b). Returns a
|
|
@@ -981,24 +1041,29 @@ export class MergedRecipe {
|
|
|
981
1041
|
this.client = client;
|
|
982
1042
|
}
|
|
983
1043
|
/** Reduce the merged output's size. See {@link Recipe.compress}. */
|
|
984
|
-
compress(optimize) {
|
|
1044
|
+
compress(optimize, options = {}) {
|
|
985
1045
|
if (optimize !== undefined && !Object.values(OptimizeFor).includes(optimize)) {
|
|
986
1046
|
const allowed = Object.values(OptimizeFor).join(', ');
|
|
987
1047
|
throw new GislConfigError(`compress 'optimize' must be one of ${allowed}; got '${String(optimize)}'.`, { reason: 'invalid_optimize', conflictingFields: ['optimize'] });
|
|
988
1048
|
}
|
|
989
|
-
return this.withStep({
|
|
1049
|
+
return this.withStep({
|
|
1050
|
+
opType: 'compress',
|
|
1051
|
+
options: { ...options, ...(optimize !== undefined ? { optimize } : {}) },
|
|
1052
|
+
});
|
|
990
1053
|
}
|
|
991
1054
|
/** Change the merged output's format. See {@link Recipe.convert}. */
|
|
992
|
-
convert(format) {
|
|
993
|
-
|
|
1055
|
+
convert(format, options = {}) {
|
|
1056
|
+
// Spread options FIRST so the explicit `format` argument is authoritative —
|
|
1057
|
+
// a `format` key in the bag must NOT silently override the call's format.
|
|
1058
|
+
return this.withStep({ opType: 'convert', options: { ...options, format } });
|
|
994
1059
|
}
|
|
995
1060
|
/** Thumbnail the merged output. Omitted dimensions are dropped from the wire options. */
|
|
996
1061
|
thumbnail(options = {}) {
|
|
997
1062
|
const wire = {};
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1063
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1064
|
+
if (value !== undefined)
|
|
1065
|
+
wire[key] = value;
|
|
1066
|
+
}
|
|
1002
1067
|
return this.withStep({ opType: 'thumbnail', options: wire });
|
|
1003
1068
|
}
|
|
1004
1069
|
/**
|
|
@@ -46,27 +46,18 @@ export const PRESETS = Object.freeze({
|
|
|
46
46
|
}),
|
|
47
47
|
"video_compress": Object.freeze({
|
|
48
48
|
Size: Object.freeze({
|
|
49
|
-
"codec": "H265",
|
|
50
49
|
"crf": 30,
|
|
51
50
|
"preset": "Slow",
|
|
52
|
-
"faststart": true,
|
|
53
|
-
"audioCodec": "Aac",
|
|
54
51
|
"audioBitrate": "_96",
|
|
55
52
|
}),
|
|
56
53
|
Balanced: Object.freeze({
|
|
57
|
-
"codec": "H264",
|
|
58
54
|
"crf": 23,
|
|
59
55
|
"preset": "Medium",
|
|
60
|
-
"faststart": true,
|
|
61
|
-
"audioCodec": "Aac",
|
|
62
56
|
"audioBitrate": "_128",
|
|
63
57
|
}),
|
|
64
58
|
Quality: Object.freeze({
|
|
65
|
-
"codec": "H264",
|
|
66
59
|
"crf": 18,
|
|
67
60
|
"preset": "Slow",
|
|
68
|
-
"faststart": true,
|
|
69
|
-
"audioCodec": "Aac",
|
|
70
61
|
"audioBitrate": "_192",
|
|
71
62
|
}),
|
|
72
63
|
}),
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const SDK_SPEC_VERSION: "1.5.0";
|
|
2
2
|
export declare const PRESET_VERSION: "1.0";
|
|
3
|
-
export declare const PRESET_CONFIG_HASH: "sha256:
|
|
3
|
+
export declare const PRESET_CONFIG_HASH: "sha256:3791bd2d0cd474c5029707f6e50480bf49be33899f7fc5c1a4e77edb136f6e95";
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
// Regenerate with: scripts/generate.py.
|
|
4
4
|
export const SDK_SPEC_VERSION = "1.5.0";
|
|
5
5
|
export const PRESET_VERSION = "1.0";
|
|
6
|
-
export const PRESET_CONFIG_HASH = "sha256:
|
|
6
|
+
export const PRESET_CONFIG_HASH = "sha256:3791bd2d0cd474c5029707f6e50480bf49be33899f7fc5c1a4e77edb136f6e95";
|
package/dist/index.core.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { parseSseStream } from './sse.js';
|
|
|
3
3
|
export type { CreditsUsageOptions, ListWorkflowsOptions, GetSchemaOptions, GetSchemaResult, PreflightClipError, PreflightClipsResult, GislClientConfig, GislSseEvent, UploadOptions, WaitOptions, WorkflowCreatePayload, OperationDef, WorkflowSourcePayload, MultiInputSourcePayload, UploadSourcePayload, JobOutputSourcePayload, ExternalImportSourcePayload, ConnectionSourcePayload, JobInputV2Payload, JobDefinitionPayload, ExternalDestinationPayload, DeliveryPayload, DeliveryModePayload, DeliveryBundleFormatPayload, DeliverySelectionPayload, DeliverySelectionTypePayload, DeliveryOutputRefPayload, WorkflowProcessingPayload, ProcessingClassHintPayload, MultipartCheckpointState, _Sdk3HandCodedUploadedPart, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignedPart, _Sdk3HandCodedPresignPartsResult, _Sdk3HandCodedKeepaliveResult, } from './types.js';
|
|
4
4
|
export { uploadSource, jobOutputSource, externalImportSource, connectionSource, } from './types.js';
|
|
5
5
|
export type { GislConfigErrorMetadata } from './errors.js';
|
|
6
|
-
export { GislError, GislApiError, GislValidationError, GislBalanceExhaustedError, GislTierRestrictedError, GislFeatureTierRestrictedError, GislFeatureNotAvailableError, GislWorkflowExpiredError, GislProbePendingError, GislAuthError, GislUploadCapExceededError, GislMultipartPartError, GislMultipartPartCountError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTimeoutError, GislAbortError, GislNetworkError, GislConfigError, GislMissingCredentialsError, GislFeatureRequiresAuthError, GislUndeclaredAssetError, GislUnusedAssetError, GislPerInputOptionsNotSupportedError, GislChainCardinalityMismatchError, GislNoSuchKeyError, GislSinkError, GislResultNotReadyError, } from './errors.js';
|
|
6
|
+
export { GislError, GislApiError, GislValidationError, GislBalanceExhaustedError, GislTierRestrictedError, GislFeatureTierRestrictedError, GislFeatureNotAvailableError, GislWorkflowExpiredError, GislProbePendingError, GislAuthError, GislUploadCapExceededError, GislMultipartPartError, GislMultipartPartCountError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTimeoutError, GislAbortError, GislNetworkError, GislConfigError, GislMissingCredentialsError, GislFeatureRequiresAuthError, GislUndeclaredAssetError, GislUnusedAssetError, GislPerInputOptionsNotSupportedError, GislChainCardinalityMismatchError, GislBundleAlreadyArchivedError, GislNoSuchKeyError, GislSinkError, GislResultNotReadyError, } from './errors.js';
|
|
7
7
|
export type { GislApiErrorOptions, GislUploadCapKind } from './errors.js';
|
|
8
8
|
export { RunResult } from './file-first.js';
|
|
9
9
|
export type { OutputFile, ItemResult, ItemFailure, Manifest, Downloader, } from './file-first.js';
|
package/dist/index.core.js
CHANGED
|
@@ -23,6 +23,9 @@ GislUndeclaredAssetError, GislUnusedAssetError, GislPerInputOptionsNotSupportedE
|
|
|
23
23
|
// methods on OperationBuilder ship; type + audit registration land
|
|
24
24
|
// here so the future chain-method PR is a pure addition).
|
|
25
25
|
GislChainCardinalityMismatchError,
|
|
26
|
+
// P4d / hv3FpLjm — double-bundle prevention; raised by `.bundle()` (wpHoJhuo).
|
|
27
|
+
// Dormant until `.bundle()` ships, so the type lands here as a pure addition.
|
|
28
|
+
GislBundleAlreadyArchivedError,
|
|
26
29
|
// FF1 / 3BIxEnfR — file-first result sink errors.
|
|
27
30
|
GislNoSuchKeyError, GislSinkError,
|
|
28
31
|
// FF5a / Ao8RPVxD — thrown by the file-first Handle.result() when the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giveitsmaller/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Node.js SDK for the GISL (Give It Smaller) file compression and processing API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"node": ">=18"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@giveitsmaller/contracts": "^0.
|
|
34
|
+
"@giveitsmaller/contracts": "^0.17.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^22",
|