@giveitsmaller/sdk 0.11.0 → 0.12.1
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 +25 -15
- package/dist/file-first.js +109 -33
- package/dist/index.core.d.ts +1 -1
- package/dist/index.core.js +3 -0
- package/package.json +1 -1
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,32 @@ 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. The `format` shorthand lowers to the `output_format` wire
|
|
339
|
+
* option (the convert op's wire key per the contract); `options` carries any
|
|
340
|
+
* additional per-op convert options.
|
|
341
|
+
*/
|
|
342
|
+
convert(format: string, options?: Record<string, unknown>): Recipe;
|
|
343
|
+
/**
|
|
344
|
+
* Generate a preview. Width and/or height in pixels; any additional per-op
|
|
345
|
+
* thumbnail options pass through. An omitted (`undefined`) value is dropped
|
|
346
|
+
* from the wire options (not sent as `undefined`).
|
|
339
347
|
*/
|
|
340
348
|
thumbnail(options?: {
|
|
341
349
|
width?: number;
|
|
342
350
|
height?: number;
|
|
343
|
-
}): Recipe;
|
|
351
|
+
} & Record<string, unknown>): Recipe;
|
|
344
352
|
/**
|
|
345
353
|
* 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
|
|
354
|
+
* secondary file) — lowers to the `text_watermark` op with a `text` option;
|
|
355
|
+
* `options` carries any additional per-op watermark options.
|
|
347
356
|
*/
|
|
348
|
-
textWatermark(text: string): Recipe;
|
|
357
|
+
textWatermark(text: string, options?: Record<string, unknown>): Recipe;
|
|
349
358
|
/**
|
|
350
359
|
* Lower this recipe to a workflow-create payload against a resolved upload
|
|
351
360
|
* id. Single-input chain → ONE job, `source: upload(fileId)`, ordered
|
|
@@ -417,6 +426,7 @@ export declare class Recipe {
|
|
|
417
426
|
private lowerStep;
|
|
418
427
|
private lowerCompressOptions;
|
|
419
428
|
private compressMediaHint;
|
|
429
|
+
private compressAudioLossless;
|
|
420
430
|
}
|
|
421
431
|
/**
|
|
422
432
|
* The homogeneous fan-out builder value (FF3a). `client.files([a, b, c])`
|
|
@@ -455,16 +465,16 @@ export declare class FilesRecipe {
|
|
|
455
465
|
* preset). Reuses {@link Recipe}'s validation — a directly-constructed
|
|
456
466
|
* lowering builds an internal Recipe that throws the same `GislConfigError`.
|
|
457
467
|
*/
|
|
458
|
-
compress(optimize?: OptimizeFor): FilesRecipe;
|
|
468
|
+
compress(optimize?: OptimizeFor, options?: Record<string, unknown>): FilesRecipe;
|
|
459
469
|
/** Change every input's format. `format` lowers verbatim to the `format` option. */
|
|
460
|
-
convert(format: string): FilesRecipe;
|
|
470
|
+
convert(format: string, options?: Record<string, unknown>): FilesRecipe;
|
|
461
471
|
/** Generate a preview of every input. Omitted dimensions are dropped from the wire options. */
|
|
462
472
|
thumbnail(options?: {
|
|
463
473
|
width?: number;
|
|
464
474
|
height?: number;
|
|
465
|
-
}): FilesRecipe;
|
|
475
|
+
} & Record<string, unknown>): FilesRecipe;
|
|
466
476
|
/** Apply the same text watermark to every input. */
|
|
467
|
-
textWatermark(text: string): FilesRecipe;
|
|
477
|
+
textWatermark(text: string, options?: Record<string, unknown>): FilesRecipe;
|
|
468
478
|
/**
|
|
469
479
|
* Combine the inputs into ONE output (N→1), in array order (FF3b). Returns a
|
|
470
480
|
* single-output {@link MergedRecipe} you chain further ops on
|
|
@@ -587,14 +597,14 @@ export declare class MergedRecipe {
|
|
|
587
597
|
private readonly client?;
|
|
588
598
|
constructor(inputs: readonly FileInput[], mergeOptions: MergeOptions, postSteps?: readonly RecipeStep[], presetDefaults?: PresetDefaults | undefined, scopedPresetDefaults?: PresetDefaults | undefined, client?: GislClient | undefined);
|
|
589
599
|
/** Reduce the merged output's size. See {@link Recipe.compress}. */
|
|
590
|
-
compress(optimize?: OptimizeFor): MergedRecipe;
|
|
600
|
+
compress(optimize?: OptimizeFor, options?: Record<string, unknown>): MergedRecipe;
|
|
591
601
|
/** Change the merged output's format. See {@link Recipe.convert}. */
|
|
592
|
-
convert(format: string): MergedRecipe;
|
|
602
|
+
convert(format: string, options?: Record<string, unknown>): MergedRecipe;
|
|
593
603
|
/** Thumbnail the merged output. Omitted dimensions are dropped from the wire options. */
|
|
594
604
|
thumbnail(options?: {
|
|
595
605
|
width?: number;
|
|
596
606
|
height?: number;
|
|
597
|
-
}): MergedRecipe;
|
|
607
|
+
} & Record<string, unknown>): MergedRecipe;
|
|
598
608
|
/**
|
|
599
609
|
* Lower to the merge DAG: one `passthrough` source job per input + one
|
|
600
610
|
* `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,56 @@ 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. The `format` shorthand lowers to the `output_format` wire
|
|
440
|
+
* option (the convert op's wire key per the contract); `options` carries any
|
|
441
|
+
* additional per-op convert options.
|
|
442
|
+
*/
|
|
443
|
+
convert(format, options = {}) {
|
|
444
|
+
// The convert op's wire key is `output_format` (contract: convert.yaml,
|
|
445
|
+
// required, all media), NOT `format`. Spread options FIRST so the explicit
|
|
446
|
+
// shorthand wins over an `output_format` key in the bag.
|
|
447
|
+
// The shorthand owns the format → a stray legacy `format` key in the bag is
|
|
448
|
+
// not a valid convert option; drop it so the wire never carries both keys.
|
|
449
|
+
const rest = { ...options };
|
|
450
|
+
delete rest.format;
|
|
451
|
+
return this.withStep({ opType: 'convert', options: { ...rest, output_format: format } });
|
|
435
452
|
}
|
|
436
453
|
/**
|
|
437
|
-
* Generate a preview. Width and/or height in pixels;
|
|
438
|
-
*
|
|
454
|
+
* Generate a preview. Width and/or height in pixels; any additional per-op
|
|
455
|
+
* thumbnail options pass through. An omitted (`undefined`) value is dropped
|
|
456
|
+
* from the wire options (not sent as `undefined`).
|
|
439
457
|
*/
|
|
440
458
|
thumbnail(options = {}) {
|
|
441
459
|
const wire = {};
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
460
|
+
for (const [key, value] of Object.entries(options)) {
|
|
461
|
+
if (value !== undefined)
|
|
462
|
+
wire[key] = value;
|
|
463
|
+
}
|
|
446
464
|
return this.withStep({ opType: 'thumbnail', options: wire });
|
|
447
465
|
}
|
|
448
466
|
/**
|
|
449
467
|
* 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
|
|
468
|
+
* secondary file) — lowers to the `text_watermark` op with a `text` option;
|
|
469
|
+
* `options` carries any additional per-op watermark options.
|
|
451
470
|
*/
|
|
452
|
-
textWatermark(text) {
|
|
453
|
-
|
|
471
|
+
textWatermark(text, options = {}) {
|
|
472
|
+
// Spread options FIRST so the explicit `text` argument is authoritative.
|
|
473
|
+
return this.withStep({ opType: 'text_watermark', options: { ...options, text } });
|
|
454
474
|
}
|
|
455
475
|
/**
|
|
456
476
|
* Lower this recipe to a workflow-create payload against a resolved upload
|
|
@@ -636,21 +656,55 @@ export class Recipe {
|
|
|
636
656
|
? { type: step.opType }
|
|
637
657
|
: { type: step.opType, options };
|
|
638
658
|
}
|
|
639
|
-
lowerCompressOptions(
|
|
640
|
-
|
|
659
|
+
lowerCompressOptions(stepOptions) {
|
|
660
|
+
// Mirror the op-first resolver precedence (OperationBuilder._resolve in
|
|
661
|
+
// builder.ts): optimize = preset layer, presetOverrides = callPresetOverride
|
|
662
|
+
// layer, the rest = explicit layer.
|
|
663
|
+
const { optimize, presetOverrides, ...explicitOptions } = stepOptions;
|
|
664
|
+
// Validate the special bag keys at this chokepoint (every compress lowers
|
|
665
|
+
// through here). The chain methods' shorthand-param guard only covers a
|
|
666
|
+
// PARAM-supplied optimize; a bag-supplied optimize / presetOverrides must be
|
|
667
|
+
// validated too, so a bad value raises the typed SDK error rather than
|
|
668
|
+
// surfacing as a raw preset-lookup error or TypeError downstream. Mirrors
|
|
669
|
+
// PHP coerceOptimize + OperationBuilder::normalisePresetOverrides.
|
|
670
|
+
if (optimize !== undefined && !Object.values(OptimizeFor).includes(optimize)) {
|
|
671
|
+
const allowed = Object.values(OptimizeFor).join(', ');
|
|
672
|
+
throw new GislConfigError(`compress 'optimize' must be one of ${allowed}; got '${String(optimize)}'.`, { reason: 'invalid_optimize', conflictingFields: ['optimize'] });
|
|
673
|
+
}
|
|
674
|
+
if (presetOverrides !== undefined &&
|
|
675
|
+
(presetOverrides === null || typeof presetOverrides !== 'object' || Array.isArray(presetOverrides))) {
|
|
676
|
+
const got = Array.isArray(presetOverrides)
|
|
677
|
+
? 'array'
|
|
678
|
+
: presetOverrides === null
|
|
679
|
+
? 'null'
|
|
680
|
+
: typeof presetOverrides;
|
|
681
|
+
throw new GislConfigError(`compress 'presetOverrides' must be a *CompressPresetOptions object; got ${got}.`, { reason: 'invalid_preset_overrides', conflictingFields: ['presetOverrides'] });
|
|
682
|
+
}
|
|
641
683
|
const media = this.compressMediaHint();
|
|
642
684
|
if (media === undefined) {
|
|
643
685
|
// Cannot infer a media class (a Blob without a recognised name, or a
|
|
644
686
|
// bare upload id) → preset resolution is impossible. Fail FAST rather
|
|
645
687
|
// than silently dropping an explicit `optimize`; bare compress() is fine.
|
|
688
|
+
// When no optimize is set, pass any explicit options through verbatim
|
|
689
|
+
// (exactly as op-first `_resolve` does when media is undefined).
|
|
646
690
|
if (optimize !== undefined) {
|
|
647
691
|
throw new GislConfigError(`compress(optimize: ${String(optimize)}) needs a media type to resolve the preset, but the ` +
|
|
648
692
|
'input has no inferable media (a pre-uploaded file id or unnamed Blob carries no extension). ' +
|
|
649
693
|
'Use a path with a file extension, or call compress() without optimize.', { reason: 'media_unknown', conflictingFields: ['optimize'] });
|
|
650
694
|
}
|
|
651
|
-
|
|
695
|
+
// presetOverrides override a resolved preset; with no media there is no
|
|
696
|
+
// preset to override, so fail fast rather than silently dropping them.
|
|
697
|
+
if (presetOverrides !== undefined) {
|
|
698
|
+
throw new GislConfigError('compress(presetOverrides) needs a media type to resolve the preset to override, but the ' +
|
|
699
|
+
'input has no inferable media (a pre-uploaded file id or unnamed Blob carries no extension). ' +
|
|
700
|
+
'Use a path with a file extension.', { reason: 'media_unknown', conflictingFields: ['presetOverrides'] });
|
|
701
|
+
}
|
|
702
|
+
return { ...explicitOptions };
|
|
703
|
+
}
|
|
704
|
+
const input = { media, op: 'compress', explicitOptions };
|
|
705
|
+
if (media === 'audio') {
|
|
706
|
+
input.audioLossless = this.compressAudioLossless();
|
|
652
707
|
}
|
|
653
|
-
const input = { media, op: 'compress', explicitOptions: {} };
|
|
654
708
|
if (this.presetDefaults !== undefined) {
|
|
655
709
|
input.presetDefaults = this.presetDefaults;
|
|
656
710
|
}
|
|
@@ -658,6 +712,11 @@ export class Recipe {
|
|
|
658
712
|
input.scopedPresetDefaults =
|
|
659
713
|
this.scopedPresetDefaults;
|
|
660
714
|
}
|
|
715
|
+
if (presetOverrides !== undefined) {
|
|
716
|
+
// Validated above to be a non-null, non-array object.
|
|
717
|
+
input.presetOverrides =
|
|
718
|
+
presetOverrides;
|
|
719
|
+
}
|
|
661
720
|
if (optimize !== undefined) {
|
|
662
721
|
input.optimize = optimize;
|
|
663
722
|
}
|
|
@@ -672,6 +731,13 @@ export class Recipe {
|
|
|
672
731
|
}
|
|
673
732
|
return undefined;
|
|
674
733
|
}
|
|
734
|
+
compressAudioLossless() {
|
|
735
|
+
if (this.input.kind === 'path')
|
|
736
|
+
return _detectAudioLossless(this.input.path);
|
|
737
|
+
if (this.input.kind === 'blob')
|
|
738
|
+
return _detectAudioLossless(this.input.blob);
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
675
741
|
}
|
|
676
742
|
/**
|
|
677
743
|
* The homogeneous fan-out builder value (FF3a). `client.files([a, b, c])`
|
|
@@ -716,20 +782,20 @@ export class FilesRecipe {
|
|
|
716
782
|
* preset). Reuses {@link Recipe}'s validation — a directly-constructed
|
|
717
783
|
* lowering builds an internal Recipe that throws the same `GislConfigError`.
|
|
718
784
|
*/
|
|
719
|
-
compress(optimize) {
|
|
720
|
-
return this.withStep(this.baseRecipe().compress(optimize));
|
|
785
|
+
compress(optimize, options = {}) {
|
|
786
|
+
return this.withStep(this.baseRecipe().compress(optimize, options));
|
|
721
787
|
}
|
|
722
788
|
/** Change every input's format. `format` lowers verbatim to the `format` option. */
|
|
723
|
-
convert(format) {
|
|
724
|
-
return this.withStep(this.baseRecipe().convert(format));
|
|
789
|
+
convert(format, options = {}) {
|
|
790
|
+
return this.withStep(this.baseRecipe().convert(format, options));
|
|
725
791
|
}
|
|
726
792
|
/** Generate a preview of every input. Omitted dimensions are dropped from the wire options. */
|
|
727
793
|
thumbnail(options = {}) {
|
|
728
794
|
return this.withStep(this.baseRecipe().thumbnail(options));
|
|
729
795
|
}
|
|
730
796
|
/** Apply the same text watermark to every input. */
|
|
731
|
-
textWatermark(text) {
|
|
732
|
-
return this.withStep(this.baseRecipe().textWatermark(text));
|
|
797
|
+
textWatermark(text, options = {}) {
|
|
798
|
+
return this.withStep(this.baseRecipe().textWatermark(text, options));
|
|
733
799
|
}
|
|
734
800
|
/**
|
|
735
801
|
* Combine the inputs into ONE output (N→1), in array order (FF3b). Returns a
|
|
@@ -981,24 +1047,34 @@ export class MergedRecipe {
|
|
|
981
1047
|
this.client = client;
|
|
982
1048
|
}
|
|
983
1049
|
/** Reduce the merged output's size. See {@link Recipe.compress}. */
|
|
984
|
-
compress(optimize) {
|
|
1050
|
+
compress(optimize, options = {}) {
|
|
985
1051
|
if (optimize !== undefined && !Object.values(OptimizeFor).includes(optimize)) {
|
|
986
1052
|
const allowed = Object.values(OptimizeFor).join(', ');
|
|
987
1053
|
throw new GislConfigError(`compress 'optimize' must be one of ${allowed}; got '${String(optimize)}'.`, { reason: 'invalid_optimize', conflictingFields: ['optimize'] });
|
|
988
1054
|
}
|
|
989
|
-
return this.withStep({
|
|
1055
|
+
return this.withStep({
|
|
1056
|
+
opType: 'compress',
|
|
1057
|
+
options: { ...options, ...(optimize !== undefined ? { optimize } : {}) },
|
|
1058
|
+
});
|
|
990
1059
|
}
|
|
991
1060
|
/** Change the merged output's format. See {@link Recipe.convert}. */
|
|
992
|
-
convert(format) {
|
|
993
|
-
|
|
1061
|
+
convert(format, options = {}) {
|
|
1062
|
+
// The convert op's wire key is `output_format` (contract: convert.yaml,
|
|
1063
|
+
// required, all media), NOT `format`. Spread options FIRST so the explicit
|
|
1064
|
+
// shorthand wins over an `output_format` key in the bag.
|
|
1065
|
+
// The shorthand owns the format → a stray legacy `format` key in the bag is
|
|
1066
|
+
// not a valid convert option; drop it so the wire never carries both keys.
|
|
1067
|
+
const rest = { ...options };
|
|
1068
|
+
delete rest.format;
|
|
1069
|
+
return this.withStep({ opType: 'convert', options: { ...rest, output_format: format } });
|
|
994
1070
|
}
|
|
995
1071
|
/** Thumbnail the merged output. Omitted dimensions are dropped from the wire options. */
|
|
996
1072
|
thumbnail(options = {}) {
|
|
997
1073
|
const wire = {};
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1074
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1075
|
+
if (value !== undefined)
|
|
1076
|
+
wire[key] = value;
|
|
1077
|
+
}
|
|
1002
1078
|
return this.withStep({ opType: 'thumbnail', options: wire });
|
|
1003
1079
|
}
|
|
1004
1080
|
/**
|
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
|