@giveitsmaller/sdk 0.6.0 → 0.7.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/_audit.js +60 -0
- package/dist/builder.d.ts +406 -0
- package/dist/builder.js +706 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.js +28 -2
- package/dist/credentials.d.ts +61 -0
- package/dist/credentials.js +200 -0
- package/dist/ergonomic/preset_resolver.d.ts +75 -0
- package/dist/ergonomic/preset_resolver.js +568 -0
- package/dist/ergonomic/presets/_translate.d.ts +11 -0
- package/dist/ergonomic/presets/_translate.js +35 -0
- package/dist/ergonomic/presets/audio_compress.d.ts +16 -0
- package/dist/ergonomic/presets/audio_compress.js +45 -0
- package/dist/ergonomic/presets/document_epub_compress.d.ts +14 -0
- package/dist/ergonomic/presets/document_epub_compress.js +34 -0
- package/dist/ergonomic/presets/document_odf_compress.d.ts +14 -0
- package/dist/ergonomic/presets/document_odf_compress.js +34 -0
- package/dist/ergonomic/presets/document_office_compress.d.ts +16 -0
- package/dist/ergonomic/presets/document_office_compress.js +40 -0
- package/dist/ergonomic/presets/document_pdf_compress.d.ts +14 -0
- package/dist/ergonomic/presets/document_pdf_compress.js +35 -0
- package/dist/ergonomic/presets/image_compress.d.ts +43 -0
- package/dist/ergonomic/presets/image_compress.js +95 -0
- package/dist/ergonomic/presets/index.d.ts +77 -0
- package/dist/ergonomic/presets/index.js +216 -0
- package/dist/ergonomic/presets/video_compress.d.ts +30 -0
- package/dist/ergonomic/presets/video_compress.js +83 -0
- package/dist/errors.d.ts +147 -1
- package/dist/errors.js +161 -0
- package/dist/generated/sdk_spec/enums.d.ts +195 -0
- package/dist/generated/sdk_spec/enums.js +127 -0
- package/dist/generated/sdk_spec/errors.d.ts +16 -0
- package/dist/generated/sdk_spec/errors.js +473 -0
- package/dist/generated/sdk_spec/index.d.ts +4 -0
- package/dist/generated/sdk_spec/index.js +7 -0
- package/dist/generated/sdk_spec/presets.d.ts +6 -0
- package/dist/generated/sdk_spec/presets.js +157 -0
- package/dist/generated/sdk_spec/version.d.ts +3 -0
- package/dist/generated/sdk_spec/version.js +6 -0
- package/dist/gisl.d.ts +112 -0
- package/dist/gisl.js +266 -0
- package/dist/index.d.ts +15 -5
- package/dist/index.js +32 -4
- package/dist/merge.d.ts +142 -0
- package/dist/merge.js +411 -0
- package/dist/types.d.ts +12 -14
- package/dist/types.js +18 -0
- package/package.json +2 -2
package/dist/merge.js
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge-compose layer for the SDK ergonomic surface (T3 / cuecCmb5).
|
|
3
|
+
*
|
|
4
|
+
* `client.merge(...assets, options?)` returns a `MergeBuilder`. The builder
|
|
5
|
+
* separates WHAT (the asset set) from ORDER (the timeline):
|
|
6
|
+
*
|
|
7
|
+
* - `merge(a, b, c)` declares the asset set — each unique input is uploaded
|
|
8
|
+
* ONCE per run, even if referenced multiple times in the sequence.
|
|
9
|
+
* - `.sequence(...refs)` defines the play order. References may repeat
|
|
10
|
+
* freely; entries may be bare asset refs or `clip(ref, opts)` objects
|
|
11
|
+
* carrying per-position options.
|
|
12
|
+
* - No `.sequence(...)` => play in declared order, no transitions.
|
|
13
|
+
*
|
|
14
|
+
* Wire-truth boundaries (lowering.md §sequences):
|
|
15
|
+
* - Video merge per-input options: `transition`, `crossfadeDuration` only.
|
|
16
|
+
* - Audio merge per-input options: `transition`, `crossfadeDuration`,
|
|
17
|
+
* `gapDuration` only.
|
|
18
|
+
* - Image merge has NO per-input options today — `clip(ref)` is reuse/order
|
|
19
|
+
* only. Per-position transitions on image merges throw locally as
|
|
20
|
+
* `GislPerInputOptionsNotSupportedError`.
|
|
21
|
+
* - No per-clip `trimStart`/`trimEnd` today (contracts ticket iZzn5QrS
|
|
22
|
+
* tracks the fix). Workaround: pre-trim each clip via a chained
|
|
23
|
+
* `compress(file, trimStart, trimEnd)`.
|
|
24
|
+
*
|
|
25
|
+
* Local validation runs BEFORE any upload — undeclared refs and unused
|
|
26
|
+
* assets both fail fast so the caller saves bandwidth on typo'd composes.
|
|
27
|
+
*/
|
|
28
|
+
import { uploadSource } from './types.js';
|
|
29
|
+
import { GislConfigError, GislPerInputOptionsNotSupportedError, GislTimeoutError, GislUndeclaredAssetError, GislUnusedAssetError, } from './errors.js';
|
|
30
|
+
import { _checkAborted, _consumeSseToTerminal, _parseMaxWait, _pollToTerminal, _projectResult, } from './builder.js';
|
|
31
|
+
/**
|
|
32
|
+
* Construct a path-asset. Bare-string arguments to `merge(...)` are
|
|
33
|
+
* implicitly wrapped via this helper.
|
|
34
|
+
*/
|
|
35
|
+
export function asset(path) {
|
|
36
|
+
return { type: 'path', path };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Wrap an already-uploaded file_id as a merge asset. Use this when the
|
|
40
|
+
* SAME logical file should be referenced from multiple merge runs with
|
|
41
|
+
* guaranteed-single-upload semantics.
|
|
42
|
+
*/
|
|
43
|
+
export function handle(fileId) {
|
|
44
|
+
return { type: 'handle', fileId };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Construct a clip entry for `.sequence(...)`. The asset MUST already
|
|
48
|
+
* be in the merge's declared asset set.
|
|
49
|
+
*/
|
|
50
|
+
export function clip(ref, options = {}) {
|
|
51
|
+
return { type: 'clip', asset: ref, options };
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// MergeBuilder
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* Captures the (declared assets, options) for a merge. `.sequence(...)`
|
|
58
|
+
* pins the play order; without it, the declared order is used as-is
|
|
59
|
+
* with no per-input options.
|
|
60
|
+
*
|
|
61
|
+
* Local validation runs at `.run()`/`.submit()` time (BEFORE any upload)
|
|
62
|
+
* and throws one of `GislUndeclaredAssetError`, `GislUnusedAssetError`,
|
|
63
|
+
* or `GislPerInputOptionsNotSupportedError` if the compose is invalid.
|
|
64
|
+
*/
|
|
65
|
+
export class MergeBuilder {
|
|
66
|
+
client;
|
|
67
|
+
assets;
|
|
68
|
+
opOptions;
|
|
69
|
+
sequenceEntries = null;
|
|
70
|
+
constructor(client, assets, opOptions) {
|
|
71
|
+
this.client = client;
|
|
72
|
+
this.assets = assets;
|
|
73
|
+
this.opOptions = opOptions;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Pin the merge play order. Each entry must reference an asset that
|
|
77
|
+
* was declared in the parent `merge(...)` call. Repeats are allowed
|
|
78
|
+
* and deduped on upload (one upload per unique declared asset).
|
|
79
|
+
*/
|
|
80
|
+
sequence(...entries) {
|
|
81
|
+
this.sequenceEntries = entries;
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
async run(options) {
|
|
85
|
+
const deadline = Date.now() + _parseMaxWait(options.maxWait);
|
|
86
|
+
const signal = options.signal;
|
|
87
|
+
const onProgress = options.onProgress;
|
|
88
|
+
const useSSE = options.useSSE ?? true;
|
|
89
|
+
// 1. Validate locally BEFORE any upload.
|
|
90
|
+
const plan = this.planSequence();
|
|
91
|
+
// 2. Upload each unique asset exactly ONCE. Pass the deadline so the
|
|
92
|
+
// upload loop can abort mid-batch on a slow connection.
|
|
93
|
+
const uploadedByAssetId = await this.uploadUniqueAssets(plan.uniqueAssets, {
|
|
94
|
+
signal,
|
|
95
|
+
onProgress,
|
|
96
|
+
deadline,
|
|
97
|
+
});
|
|
98
|
+
_checkAborted(signal);
|
|
99
|
+
if (Date.now() >= deadline) {
|
|
100
|
+
throw new GislTimeoutError(`Upload(s) completed but maxWait elapsed before merge workflow could be created`);
|
|
101
|
+
}
|
|
102
|
+
// 3. Build the merge JobDefinitionPayload (multi-input).
|
|
103
|
+
const payload = this.buildPayload(plan, uploadedByAssetId);
|
|
104
|
+
const created = await this.client.createWorkflow(payload);
|
|
105
|
+
_checkAborted(signal);
|
|
106
|
+
// 4. Wait to terminal status.
|
|
107
|
+
const finalStatus = await this.awaitTerminal({
|
|
108
|
+
workflowId: created.workflowId,
|
|
109
|
+
deadline,
|
|
110
|
+
signal,
|
|
111
|
+
onProgress,
|
|
112
|
+
useSSE,
|
|
113
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
114
|
+
});
|
|
115
|
+
// 5. Fetch downloads + project.
|
|
116
|
+
if (Date.now() >= deadline) {
|
|
117
|
+
throw new GislTimeoutError(`Merge workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`);
|
|
118
|
+
}
|
|
119
|
+
const downloads = await this.client.getWorkflowDownloads(created.workflowId);
|
|
120
|
+
return _projectResult(finalStatus, downloads.downloads, this.opOptionsForResolved());
|
|
121
|
+
}
|
|
122
|
+
async submit(options) {
|
|
123
|
+
const plan = this.planSequence();
|
|
124
|
+
const uploadedByAssetId = await this.uploadUniqueAssets(plan.uniqueAssets, {});
|
|
125
|
+
const payload = this.buildPayload(plan, uploadedByAssetId);
|
|
126
|
+
payload.callback_url = options.webhook;
|
|
127
|
+
const created = await this.client.createWorkflow(payload);
|
|
128
|
+
const handle = {
|
|
129
|
+
workflowId: created.workflowId,
|
|
130
|
+
...(created.webhookSecret != null ? { webhookSecret: created.webhookSecret } : {}),
|
|
131
|
+
};
|
|
132
|
+
return handle;
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
/**
|
|
136
|
+
* Resolve the declared assets + sequence (or fall back to declared order),
|
|
137
|
+
* dedupe by identity, and run the local validators. The returned plan
|
|
138
|
+
* carries the SEQUENCE (positional entries) + the UNIQUE assets to upload.
|
|
139
|
+
*/
|
|
140
|
+
planSequence() {
|
|
141
|
+
const declaredIds = this.assets.map(assetIdentity);
|
|
142
|
+
const declaredSet = new Map();
|
|
143
|
+
for (let i = 0; i < this.assets.length; i += 1) {
|
|
144
|
+
const id = declaredIds[i];
|
|
145
|
+
if (!declaredSet.has(id))
|
|
146
|
+
declaredSet.set(id, this.assets[i]);
|
|
147
|
+
}
|
|
148
|
+
// Use the explicit sequence if set; otherwise the declared order as-is.
|
|
149
|
+
const rawEntries = this.sequenceEntries ?? this.assets.map((a) => a);
|
|
150
|
+
// Validate per-entry: undeclared ref + image-merge-clip-with-opts.
|
|
151
|
+
const mediaKind = this.inferMediaKind();
|
|
152
|
+
const positions = [];
|
|
153
|
+
const refIds = new Set();
|
|
154
|
+
for (const entry of rawEntries) {
|
|
155
|
+
const isClip = entry !== null && typeof entry === 'object' && 'type' in entry && entry.type === 'clip';
|
|
156
|
+
const assetRef = isClip ? entry.asset : entry;
|
|
157
|
+
const id = assetIdentity(assetRef);
|
|
158
|
+
if (!declaredSet.has(id)) {
|
|
159
|
+
throw new GislUndeclaredAssetError(id, Array.from(declaredSet.keys()));
|
|
160
|
+
}
|
|
161
|
+
refIds.add(id);
|
|
162
|
+
if (isClip) {
|
|
163
|
+
const opts = entry.options;
|
|
164
|
+
const hasOpts = opts.transition !== undefined ||
|
|
165
|
+
opts.crossfadeDuration !== undefined ||
|
|
166
|
+
opts.gapDuration !== undefined;
|
|
167
|
+
if (hasOpts && mediaKind === 'image') {
|
|
168
|
+
throw new GislPerInputOptionsNotSupportedError('image');
|
|
169
|
+
}
|
|
170
|
+
positions.push({ assetId: id, options: opts });
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
positions.push({ assetId: id, options: {} });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Unused-asset check.
|
|
177
|
+
if (this.sequenceEntries !== null && this.opOptions.allowUnusedAssets !== true) {
|
|
178
|
+
const unused = Array.from(declaredSet.keys()).filter((id) => !refIds.has(id));
|
|
179
|
+
if (unused.length > 0)
|
|
180
|
+
throw new GislUnusedAssetError(unused);
|
|
181
|
+
}
|
|
182
|
+
// Codex r1 medium edb1bb641d81 — when an explicit sequence is set,
|
|
183
|
+
// restrict the upload set to only the referenced assets. This prevents
|
|
184
|
+
// wasted uploads of declared-but-unsequenced assets (e.g. when the
|
|
185
|
+
// user passes allowUnusedAssets: true).
|
|
186
|
+
const uploadSet = this.sequenceEntries === null
|
|
187
|
+
? declaredSet
|
|
188
|
+
: new Map(Array.from(declaredSet.entries()).filter(([id]) => refIds.has(id)));
|
|
189
|
+
// Codex r1 medium 5c86b67c979b — enforce merge schema input bounds
|
|
190
|
+
// (min_inputs: 2, max_inputs: 10 per generated/typescript/operations/merge.ts).
|
|
191
|
+
// Validate sequence position count, NOT unique-asset count: the merge job
|
|
192
|
+
// sends N inputs where N = position count (repeats included).
|
|
193
|
+
if (positions.length < 2) {
|
|
194
|
+
throw new GislConfigError(`merge requires at least 2 inputs (got ${positions.length}). Declare more assets or check the sequence.`);
|
|
195
|
+
}
|
|
196
|
+
if (positions.length > 10) {
|
|
197
|
+
throw new GislConfigError(`merge accepts at most 10 inputs (got ${positions.length}). Reduce the sequence or split the merge.`);
|
|
198
|
+
}
|
|
199
|
+
return { mediaKind, positions, uniqueAssets: uploadSet };
|
|
200
|
+
}
|
|
201
|
+
inferMediaKind() {
|
|
202
|
+
if (this.opOptions.mediaKind !== undefined)
|
|
203
|
+
return this.opOptions.mediaKind;
|
|
204
|
+
const first = this.assets[0];
|
|
205
|
+
if (first === undefined)
|
|
206
|
+
return 'video';
|
|
207
|
+
if (first.type === 'path' && typeof first.path === 'string') {
|
|
208
|
+
const lower = first.path.toLowerCase();
|
|
209
|
+
if (/\.(jpe?g|png|webp|avif|gif|heic|tiff?)$/.test(lower))
|
|
210
|
+
return 'image';
|
|
211
|
+
if (/\.(mp3|wav|flac|aac|ogg|m4a)$/.test(lower))
|
|
212
|
+
return 'audio';
|
|
213
|
+
return 'video';
|
|
214
|
+
}
|
|
215
|
+
if (first.type === 'path' && first.path instanceof Blob) {
|
|
216
|
+
if (first.path.type.startsWith('image/'))
|
|
217
|
+
return 'image';
|
|
218
|
+
if (first.path.type.startsWith('audio/'))
|
|
219
|
+
return 'audio';
|
|
220
|
+
return 'video';
|
|
221
|
+
}
|
|
222
|
+
return 'video';
|
|
223
|
+
}
|
|
224
|
+
async uploadUniqueAssets(uniqueAssets, opts) {
|
|
225
|
+
const uploaded = new Map();
|
|
226
|
+
for (const [id, a] of uniqueAssets) {
|
|
227
|
+
// Codex r1 medium 797b4113431f — check the deadline between uploads
|
|
228
|
+
// so a multi-file merge doesn't keep uploading past `maxWait`.
|
|
229
|
+
if (opts.deadline !== undefined && Date.now() >= opts.deadline) {
|
|
230
|
+
throw new GislTimeoutError(`maxWait elapsed mid-upload (after ${uploaded.size} of ${uniqueAssets.size} merge assets)`);
|
|
231
|
+
}
|
|
232
|
+
if (a.type === 'handle') {
|
|
233
|
+
uploaded.set(id, a.fileId);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const uploadOpts = {};
|
|
237
|
+
if (opts.signal !== undefined)
|
|
238
|
+
uploadOpts.signal = opts.signal;
|
|
239
|
+
if (opts.onProgress !== undefined) {
|
|
240
|
+
uploadOpts.onProgress = (uploadedBytes, totalBytes) => {
|
|
241
|
+
opts.onProgress?.({ phase: 'upload', uploadedBytes, totalBytes });
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const resp = await this.client.uploadFile(a.path, uploadOpts);
|
|
245
|
+
uploaded.set(id, resp.fileId);
|
|
246
|
+
}
|
|
247
|
+
return uploaded;
|
|
248
|
+
}
|
|
249
|
+
buildPayload(plan, uploadedByAssetId) {
|
|
250
|
+
const inputs = plan.positions.map((pos) => {
|
|
251
|
+
const fileId = uploadedByAssetId.get(pos.assetId);
|
|
252
|
+
if (fileId === undefined) {
|
|
253
|
+
// Defensive — planSequence should have rejected this.
|
|
254
|
+
throw new Error(`Asset '${pos.assetId}' was never uploaded — internal builder bug`);
|
|
255
|
+
}
|
|
256
|
+
// Codex r1 HIGH 502c6bf232c2 — per_input_options goes on EACH
|
|
257
|
+
// JobInputV2Payload (per-input entry), NOT on operations[0].options.
|
|
258
|
+
// Skip emission for image merges (planSequence already rejects opts
|
|
259
|
+
// on image-merge clips). Project per ClipOptions per media kind
|
|
260
|
+
// (codex r1 medium 128404fa16a9 — gapDuration is audio-only).
|
|
261
|
+
const wireOpts = plan.mediaKind === 'image'
|
|
262
|
+
? {}
|
|
263
|
+
: wirePerInputOptions(pos.options, plan.mediaKind);
|
|
264
|
+
const input = { source: uploadSource(fileId) };
|
|
265
|
+
if (Object.keys(wireOpts).length > 0) {
|
|
266
|
+
input.per_input_options = wireOpts;
|
|
267
|
+
}
|
|
268
|
+
return input;
|
|
269
|
+
});
|
|
270
|
+
// Merge-level options (excluding the SDK-side mediaKind/allowUnusedAssets).
|
|
271
|
+
const mergeOpts = wireMergeOptions(this.opOptions, plan.mediaKind);
|
|
272
|
+
const job = {
|
|
273
|
+
id: 'merge',
|
|
274
|
+
inputs,
|
|
275
|
+
operations: [{ type: 'merge', options: mergeOpts }],
|
|
276
|
+
};
|
|
277
|
+
return { jobs: [job] };
|
|
278
|
+
}
|
|
279
|
+
opOptionsForResolved() {
|
|
280
|
+
// Strip the SDK-only fields before exposing on resolvedOptions.applied.
|
|
281
|
+
const { mediaKind: _m, allowUnusedAssets: _a, ...rest } = this.opOptions;
|
|
282
|
+
void _m;
|
|
283
|
+
void _a;
|
|
284
|
+
return { ...rest };
|
|
285
|
+
}
|
|
286
|
+
async awaitTerminal(args) {
|
|
287
|
+
if (args.useSSE) {
|
|
288
|
+
try {
|
|
289
|
+
return await _consumeSseToTerminal(this.client, args);
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
if (err instanceof GislTimeoutError)
|
|
293
|
+
throw err;
|
|
294
|
+
if (err instanceof DOMException && err.name === 'AbortError')
|
|
295
|
+
throw err;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return await _pollToTerminal(this.client, args);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Per-Blob identity tokens — referential dedupe via WeakMap. Two distinct
|
|
303
|
+
* Blob objects with the same size + MIME would otherwise hash to the
|
|
304
|
+
* SAME identity (silent data loss; code-reviewer P1 conf 8). Reference
|
|
305
|
+
* identity guarantees same-Blob = same-upload and different-Blob = two
|
|
306
|
+
* uploads, irrespective of content sniffing.
|
|
307
|
+
*/
|
|
308
|
+
const _blobTokens = new WeakMap();
|
|
309
|
+
let _blobCounter = 0;
|
|
310
|
+
/**
|
|
311
|
+
* Asset identity for dedupe. Handles use their fileId; paths use a
|
|
312
|
+
* trim+trailing-separator-strip normalised string (NOT case-folded —
|
|
313
|
+
* case-insensitive dedupe would silently merge `A.mp4` and `a.mp4` on a
|
|
314
|
+
* case-sensitive filesystem; code-reviewer P1 conf 7). Blobs use
|
|
315
|
+
* referential identity via a WeakMap-backed token. Bare-string path
|
|
316
|
+
* dedupe is best-effort — use `handle()` for guaranteed reuse.
|
|
317
|
+
*/
|
|
318
|
+
function assetIdentity(a) {
|
|
319
|
+
if (a.type === 'handle')
|
|
320
|
+
return `handle:${a.fileId}`;
|
|
321
|
+
if (a.path instanceof Blob) {
|
|
322
|
+
let token = _blobTokens.get(a.path);
|
|
323
|
+
if (token === undefined) {
|
|
324
|
+
_blobCounter += 1;
|
|
325
|
+
token = `${_blobCounter}`;
|
|
326
|
+
_blobTokens.set(a.path, token);
|
|
327
|
+
}
|
|
328
|
+
return `blob:${token}`;
|
|
329
|
+
}
|
|
330
|
+
// Codex r2 medium bb500566a683 — dedupe by the EXACT caller-provided
|
|
331
|
+
// string. Previous trim+trailing-slash-strip would collapse
|
|
332
|
+
// `'clip.mp4'` and `'clip.mp4 '` into one upload while uploadFile later
|
|
333
|
+
// received the original string. Exact-string dedupe = upload identity
|
|
334
|
+
// matches dedupe identity. Best-effort = "two identical strings dedupe;
|
|
335
|
+
// anything else is a separate upload" — predictable.
|
|
336
|
+
return `path:${a.path}`;
|
|
337
|
+
}
|
|
338
|
+
function wireMergeOptions(opts, mediaKind) {
|
|
339
|
+
const out = {};
|
|
340
|
+
if (opts.transition !== undefined)
|
|
341
|
+
out.transition = opts.transition;
|
|
342
|
+
if (opts.crossfadeDuration !== undefined)
|
|
343
|
+
out.crossfade_duration = opts.crossfadeDuration;
|
|
344
|
+
// Codex r2 medium ab2422e56ea0 — merge-level `gap_duration` is on
|
|
345
|
+
// MergeAudioOptions only (not MergeVideoOptions or MergeImageOptions).
|
|
346
|
+
// Drop it for non-audio merges instead of shipping an invalid payload.
|
|
347
|
+
if (opts.gapDuration !== undefined && mediaKind === 'audio')
|
|
348
|
+
out.gap_duration = opts.gapDuration;
|
|
349
|
+
if (opts.normalizeAudio !== undefined)
|
|
350
|
+
out.normalize_audio = opts.normalizeAudio;
|
|
351
|
+
if (opts.codec !== undefined)
|
|
352
|
+
out.codec = opts.codec;
|
|
353
|
+
if (opts.crf !== undefined)
|
|
354
|
+
out.crf = opts.crf;
|
|
355
|
+
if (opts.preset !== undefined)
|
|
356
|
+
out.preset = opts.preset;
|
|
357
|
+
if (opts.targetSize !== undefined) {
|
|
358
|
+
out.target_size_bytes = typeof opts.targetSize === 'number'
|
|
359
|
+
? opts.targetSize
|
|
360
|
+
: parseSizeString(opts.targetSize);
|
|
361
|
+
out.encoding_mode = 'target_size';
|
|
362
|
+
}
|
|
363
|
+
if (opts.transitionDuration !== undefined)
|
|
364
|
+
out.transition_duration = opts.transitionDuration;
|
|
365
|
+
if (opts.fps !== undefined)
|
|
366
|
+
out.fps = opts.fps;
|
|
367
|
+
if (opts.durationPerImage !== undefined)
|
|
368
|
+
out.duration_per_image = opts.durationPerImage;
|
|
369
|
+
if (opts.loopCount !== undefined)
|
|
370
|
+
out.loop_count = opts.loopCount;
|
|
371
|
+
if (opts.output !== undefined)
|
|
372
|
+
out.output_type = opts.output;
|
|
373
|
+
if (opts.outputType !== undefined)
|
|
374
|
+
out.output_type = opts.outputType;
|
|
375
|
+
if (opts.videoFormat !== undefined)
|
|
376
|
+
out.video_format = opts.videoFormat;
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Project a ClipOptions into the wire-shape per_input_options object.
|
|
381
|
+
* Codex r1 medium 128404fa16a9 — `gap_duration` is on AUDIO per-input
|
|
382
|
+
* only (`MergeAudioPerInputOptions`), NOT video. Splitting by mediaKind
|
|
383
|
+
* here keeps the wire payload honest and prevents the server from
|
|
384
|
+
* silently rejecting/ignoring an out-of-spec field.
|
|
385
|
+
*/
|
|
386
|
+
function wirePerInputOptions(opts, mediaKind) {
|
|
387
|
+
const out = {};
|
|
388
|
+
if (opts.transition !== undefined)
|
|
389
|
+
out.transition = opts.transition;
|
|
390
|
+
if (opts.crossfadeDuration !== undefined)
|
|
391
|
+
out.crossfade_duration = opts.crossfadeDuration;
|
|
392
|
+
if (opts.gapDuration !== undefined && mediaKind === 'audio') {
|
|
393
|
+
out.gap_duration = opts.gapDuration;
|
|
394
|
+
}
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
function parseSizeString(s) {
|
|
398
|
+
const m = /^(\d+(?:\.\d+)?)\s*(KB|MB|GB|B)?$/i.exec(s.trim());
|
|
399
|
+
if (m === null)
|
|
400
|
+
throw new TypeError(`Invalid targetSize string '${s}'`);
|
|
401
|
+
const n = Number(m[1]);
|
|
402
|
+
const unit = (m[2] ?? 'B').toUpperCase();
|
|
403
|
+
switch (unit) {
|
|
404
|
+
case 'B': return Math.round(n);
|
|
405
|
+
case 'KB': return Math.round(n * 1_000);
|
|
406
|
+
case 'MB': return Math.round(n * 1_000_000);
|
|
407
|
+
case 'GB': return Math.round(n * 1_000_000_000);
|
|
408
|
+
/* istanbul ignore next */
|
|
409
|
+
default: throw new TypeError(`Unknown size unit '${unit}'`);
|
|
410
|
+
}
|
|
411
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -94,21 +94,19 @@ export interface JobDefinitionPayload {
|
|
|
94
94
|
operations: OperationDef[];
|
|
95
95
|
/** Per-job hide-intermediates promotion flag per ADR-0003. */
|
|
96
96
|
deliver?: boolean;
|
|
97
|
-
/**
|
|
98
|
-
* Per-job opt-out of the "compress required in every chain" gate.
|
|
99
|
-
* When `true`, the server accepts a chain that doesn't terminate in a
|
|
100
|
-
* `compress` operation — required for chains that observe multi-output
|
|
101
|
-
* fan-out (e.g. convert PDF -> N images per ADR-0009 §D2) without
|
|
102
|
-
* collapsing the N outputs through a trailing chained compress.
|
|
103
|
-
*
|
|
104
|
-
* Accepted by the API at `compression/src/Jobs/.../JobDefinition.php`
|
|
105
|
-
* (`skipCompression`) and validated against the chain-ordering rule at
|
|
106
|
-
* `Job::validateChainOrdering`. Currently undocumented in
|
|
107
|
-
* `contracts/openapi/api.yaml` JobDefinition schema — spec follow-up
|
|
108
|
-
* pending; the SDK exposes the field to unblock e2e A8-FLIP.
|
|
109
|
-
*/
|
|
110
|
-
skip_compression?: boolean;
|
|
111
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Single source of truth for JobDefinitionPayload's top-level wire keys.
|
|
100
|
+
* Read by `contract-drift-fields.test.ts` to cross-check against the spec
|
|
101
|
+
* at `JobDefinition`. Not re-exported from `index.ts`; this is reachable
|
|
102
|
+
* only via deep imports and should not be treated as public API.
|
|
103
|
+
*
|
|
104
|
+
* The V1-only `skip_compression` field is deliberately absent (eQnUMW68);
|
|
105
|
+
* the runtime drift test will fail if the spec re-introduces it OR adds
|
|
106
|
+
* any other V1-leak field to the V2 JobDefinition schema.
|
|
107
|
+
* @internal
|
|
108
|
+
*/
|
|
109
|
+
export declare const JOB_DEFINITION_PAYLOAD_KEYS: readonly ["id", "source", "inputs", "operations", "deliver"];
|
|
112
110
|
export type ExternalDestinationPayload = {
|
|
113
111
|
type: 'connection';
|
|
114
112
|
connection_id: string;
|
package/dist/types.js
CHANGED
|
@@ -15,6 +15,24 @@ export function externalImportSource(externalSourceId) {
|
|
|
15
15
|
export function connectionSource(connectionId, path) {
|
|
16
16
|
return { type: 'connection', connection_id: connectionId, path };
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Single source of truth for JobDefinitionPayload's top-level wire keys.
|
|
20
|
+
* Read by `contract-drift-fields.test.ts` to cross-check against the spec
|
|
21
|
+
* at `JobDefinition`. Not re-exported from `index.ts`; this is reachable
|
|
22
|
+
* only via deep imports and should not be treated as public API.
|
|
23
|
+
*
|
|
24
|
+
* The V1-only `skip_compression` field is deliberately absent (eQnUMW68);
|
|
25
|
+
* the runtime drift test will fail if the spec re-introduces it OR adds
|
|
26
|
+
* any other V1-leak field to the V2 JobDefinition schema.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export const JOB_DEFINITION_PAYLOAD_KEYS = Object.freeze([
|
|
30
|
+
'id',
|
|
31
|
+
'source',
|
|
32
|
+
'inputs',
|
|
33
|
+
'operations',
|
|
34
|
+
'deliver',
|
|
35
|
+
]);
|
|
18
36
|
/**
|
|
19
37
|
* Single source of truth for WorkflowCreatePayload's top-level wire keys.
|
|
20
38
|
* Read by `contract-drift-fields.test.ts` to cross-check against the spec at
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giveitsmaller/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"node": ">=18"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@giveitsmaller/contracts": "^0.
|
|
22
|
+
"@giveitsmaller/contracts": "^0.8.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^22",
|