vidspotai-shared 1.0.117 → 1.0.119
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/lib/globals/aiModels/enums.d.ts +22 -1
- package/lib/globals/aiModels/enums.d.ts.map +1 -1
- package/lib/globals/aiModels/enums.js +22 -1
- package/lib/globals/aiModels/index.d.ts.map +1 -1
- package/lib/globals/aiModels/index.js +2 -0
- package/lib/globals/aiModels/providers/topaz.d.ts +21 -0
- package/lib/globals/aiModels/providers/topaz.d.ts.map +1 -0
- package/lib/globals/aiModels/providers/topaz.js +57 -0
- package/lib/globals/aiModels/types.d.ts +7 -1
- package/lib/globals/aiModels/types.d.ts.map +1 -1
- package/lib/globals/types.d.ts +26 -1
- package/lib/globals/types.d.ts.map +1 -1
- package/lib/globals/types.js +31 -1
- package/lib/libs/storage/MediaStorage.d.ts +4 -3
- package/lib/libs/storage/MediaStorage.d.ts.map +1 -1
- package/lib/models/index.d.ts +2 -0
- package/lib/models/index.d.ts.map +1 -1
- package/lib/models/index.js +2 -0
- package/lib/models/posterProject.model.d.ts +30 -0
- package/lib/models/posterProject.model.d.ts.map +1 -0
- package/lib/models/posterProject.model.js +2 -0
- package/lib/models/upscale.model.d.ts +57 -0
- package/lib/models/upscale.model.d.ts.map +1 -0
- package/lib/models/upscale.model.js +2 -0
- package/lib/schemas/project.schema.d.ts +226 -0
- package/lib/schemas/project.schema.d.ts.map +1 -1
- package/lib/schemas/project.schema.js +45 -1
- package/lib/schemas/videoPlan.schema.d.ts +3 -3
- package/lib/services/agent/tools/composeScene.tool.d.ts +204 -0
- package/lib/services/agent/tools/composeScene.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/estimateCost.tool.d.ts +68 -0
- package/lib/services/agent/tools/estimateCost.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/planVideo.tool.d.ts +1 -1
- package/lib/services/agent/tools/render.tool.d.ts +68 -0
- package/lib/services/agent/tools/render.tool.d.ts.map +1 -1
- package/lib/services/aiGen/aiGenFactory.service.d.ts +19 -1
- package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
- package/lib/services/aiGen/aiGenFactory.service.js +38 -1
- package/lib/services/aiGen/providers/baseAiGenProvider.service.d.ts +13 -1
- package/lib/services/aiGen/providers/baseAiGenProvider.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/baseAiGenProvider.service.js +20 -0
- package/lib/services/aiGen/providers/fal/falUpscale.service.d.ts +38 -0
- package/lib/services/aiGen/providers/fal/falUpscale.service.d.ts.map +1 -0
- package/lib/services/aiGen/providers/fal/falUpscale.service.js +226 -0
- package/lib/services/aiGen/providers/fal/index.d.ts +1 -0
- package/lib/services/aiGen/providers/fal/index.d.ts.map +1 -1
- package/lib/services/aiGen/providers/fal/index.js +1 -0
- package/lib/services/aiGen/providers/openai/openai.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/openai/openai.service.js +88 -56
- package/lib/services/aiGen/providers/topaz/topaz.service.d.ts +51 -0
- package/lib/services/aiGen/providers/topaz/topaz.service.d.ts.map +1 -0
- package/lib/services/aiGen/providers/topaz/topaz.service.js +321 -0
- package/lib/services/aiGen/providers/types.d.ts +73 -2
- package/lib/services/aiGen/providers/types.d.ts.map +1 -1
- package/lib/services/editor/designToProject.d.ts +10 -1
- package/lib/services/editor/designToProject.d.ts.map +1 -1
- package/lib/services/editor/designToProject.js +78 -8
- package/lib/services/editor/projectToDesign.d.ts.map +1 -1
- package/lib/services/editor/projectToDesign.js +38 -2
- package/lib/services/firestore.service.d.ts +4 -0
- package/lib/services/firestore.service.d.ts.map +1 -1
- package/lib/services/firestore.service.js +6 -0
- package/lib/services/index.d.ts +1 -0
- package/lib/services/index.d.ts.map +1 -1
- package/lib/services/index.js +1 -0
- package/lib/services/moderation/index.d.ts +3 -0
- package/lib/services/moderation/index.d.ts.map +1 -0
- package/lib/services/moderation/index.js +5 -0
- package/lib/services/moderation/textModeration.service.d.ts +6 -0
- package/lib/services/moderation/textModeration.service.d.ts.map +1 -0
- package/lib/services/moderation/textModeration.service.js +44 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/openai/openai.service.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAclB,qBAAa,aAAc,SAAQ,wBAAwB;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,MAAM,CAAS;;IAQjB,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAwF3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA8B3C,YAAY,CAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IA2ChC;;;;OAIG;IACG,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"openai.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/openai/openai.service.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAclB,qBAAa,aAAc,SAAQ,wBAAwB;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,MAAM,CAAS;;IAQjB,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAwF3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA8B3C,YAAY,CAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IA2ChC;;;;OAIG;IACG,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IA6JjC,aAAa,CAAC,EACZ,QAAQ,EACR,UAAuB,EACvB,QAAY,EACZ,SAAiB,EACjB,SAAa,EACb,OAAO,EACP,WAAW,GACZ,EAAE,iBAAiB,GAAG,MAAM;CAqB9B"}
|
|
@@ -235,70 +235,102 @@ class OpenaiService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
235
235
|
// non-streaming overload always applies; TS can't narrow the union
|
|
236
236
|
// automatically from the absence of a field.
|
|
237
237
|
let resp;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
logger_1.logger.error("openai: ref fetch failed", {
|
|
243
|
-
url,
|
|
244
|
-
status: r.status,
|
|
245
|
-
statusText: r.statusText,
|
|
246
|
-
});
|
|
247
|
-
throw new Error(`openai ref fetch ${r.status}`);
|
|
248
|
-
}
|
|
249
|
-
const name = (url.split("?")[0] ?? "ref").split("/").pop() || "ref.png";
|
|
250
|
-
return (0, openai_1.toFile)(r, name, { type: imageMimeFromUrl(url) });
|
|
251
|
-
}));
|
|
252
|
-
const mask = params.maskImageUrl
|
|
253
|
-
? await (async () => {
|
|
254
|
-
const r = await (0, storage_1.fetchOwnMedia)(params.maskImageUrl);
|
|
238
|
+
try {
|
|
239
|
+
if (isEdit) {
|
|
240
|
+
const images = await Promise.all(refs.map(async (url) => {
|
|
241
|
+
const r = await (0, storage_1.fetchOwnMedia)(url);
|
|
255
242
|
if (!r.ok) {
|
|
256
|
-
logger_1.logger.error("openai:
|
|
257
|
-
url
|
|
243
|
+
logger_1.logger.error("openai: ref fetch failed", {
|
|
244
|
+
url,
|
|
258
245
|
status: r.status,
|
|
259
246
|
statusText: r.statusText,
|
|
260
247
|
});
|
|
261
|
-
throw new Error(`openai
|
|
248
|
+
throw new Error(`openai ref fetch ${r.status}`);
|
|
262
249
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
250
|
+
const name = (url.split("?")[0] ?? "ref").split("/").pop() || "ref.png";
|
|
251
|
+
return (0, openai_1.toFile)(r, name, { type: imageMimeFromUrl(url) });
|
|
252
|
+
}));
|
|
253
|
+
const mask = params.maskImageUrl
|
|
254
|
+
? await (async () => {
|
|
255
|
+
const r = await (0, storage_1.fetchOwnMedia)(params.maskImageUrl);
|
|
256
|
+
if (!r.ok) {
|
|
257
|
+
logger_1.logger.error("openai: mask fetch failed", {
|
|
258
|
+
url: params.maskImageUrl,
|
|
259
|
+
status: r.status,
|
|
260
|
+
statusText: r.statusText,
|
|
261
|
+
});
|
|
262
|
+
throw new Error(`openai mask fetch ${r.status}`);
|
|
263
|
+
}
|
|
264
|
+
return (0, openai_1.toFile)(r, "mask.png", { type: imageMimeFromUrl(params.maskImageUrl) });
|
|
265
|
+
})()
|
|
266
|
+
: undefined;
|
|
267
|
+
resp = (await this.client.images.edit({
|
|
268
|
+
model: modelId,
|
|
269
|
+
prompt: params.prompt,
|
|
270
|
+
image: images.length === 1 ? images[0] : images,
|
|
271
|
+
...(mask ? { mask } : {}),
|
|
272
|
+
...(params.numImages ? { n: params.numImages } : {}),
|
|
273
|
+
...(size ? { size: size } : {}),
|
|
274
|
+
...(params.quality ? { quality: params.quality } : {}),
|
|
275
|
+
...(params.background ? { background: params.background } : {}),
|
|
276
|
+
...(params.outputFormat ? { output_format: params.outputFormat } : {}),
|
|
277
|
+
}));
|
|
278
|
+
if (!resp?.data?.length) {
|
|
279
|
+
logger_1.logger.error("openai: images endpoint returned no data", {
|
|
280
|
+
endpoint: "edit",
|
|
281
|
+
responseKeys: resp ? Object.keys(resp) : null,
|
|
282
|
+
});
|
|
283
|
+
throw new Error("openai images: empty data[]");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
resp = (await this.client.images.generate({
|
|
288
|
+
model: modelId,
|
|
289
|
+
prompt: params.prompt,
|
|
290
|
+
...(params.numImages ? { n: params.numImages } : {}),
|
|
291
|
+
...(size ? { size: size } : {}),
|
|
292
|
+
...(params.quality ? { quality: params.quality } : {}),
|
|
293
|
+
...(params.background ? { background: params.background } : {}),
|
|
294
|
+
...(params.outputFormat ? { output_format: params.outputFormat } : {}),
|
|
295
|
+
}));
|
|
296
|
+
if (!resp?.data?.length) {
|
|
297
|
+
logger_1.logger.error("openai: images endpoint returned no data", {
|
|
298
|
+
endpoint: "generate",
|
|
299
|
+
responseKeys: resp ? Object.keys(resp) : null,
|
|
300
|
+
});
|
|
301
|
+
throw new Error("openai images: empty data[]");
|
|
302
|
+
}
|
|
283
303
|
}
|
|
284
304
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
305
|
+
catch (err) {
|
|
306
|
+
// Only remap genuine OpenAI APIErrors (they carry `.status`). The
|
|
307
|
+
// ref/mask-fetch and empty-data[] errors thrown above are our own plain
|
|
308
|
+
// Errors with no `.status`, so they fall through unchanged and keep
|
|
309
|
+
// paging — those ARE ops-actionable (our storage/CDN, not user input).
|
|
310
|
+
const status = err?.status ?? err?.response?.status;
|
|
311
|
+
if (status === undefined)
|
|
312
|
+
throw err;
|
|
313
|
+
const message = err?.message ?? String(err);
|
|
314
|
+
if (status === 400) {
|
|
315
|
+
// The images.edit endpoint's most common 400 is a rejected reference
|
|
316
|
+
// image (unsupported mode/corrupt file/size mismatch) — user-fixable,
|
|
317
|
+
// not an ops incident. Fall back to the generic capability-mismatch
|
|
318
|
+
// code for other 400s (e.g. bad size/quality combination).
|
|
319
|
+
const code = isEdit && /image/i.test(message)
|
|
320
|
+
? errors_1.USER_FACING_ERROR_CODES.INPUT_IMAGE_REJECTED
|
|
321
|
+
: errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH;
|
|
322
|
+
throw new errors_1.UserFacingError(message, code);
|
|
323
|
+
}
|
|
324
|
+
if (status === 401 || status === 403) {
|
|
325
|
+
throw new errors_1.UserFacingError(message, errors_1.USER_FACING_ERROR_CODES.PROVIDER_AUTH_ERROR);
|
|
301
326
|
}
|
|
327
|
+
if (status === 429) {
|
|
328
|
+
const oaCode = err?.code ?? err?.error?.code ?? err?.type ?? err?.error?.type ?? "";
|
|
329
|
+
const isQuota = /insufficient_quota|billing|exceeded_current_quota/i.test(oaCode) ||
|
|
330
|
+
/quota|billing|insufficient/i.test(message);
|
|
331
|
+
throw new errors_1.UserFacingError(message, isQuota ? errors_1.USER_FACING_ERROR_CODES.ACCOUNT_QUOTA_EXCEEDED : errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
|
|
332
|
+
}
|
|
333
|
+
throw err;
|
|
302
334
|
}
|
|
303
335
|
const data = resp.data ?? [];
|
|
304
336
|
const ts = Date.now();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { BaseAiGenProviderService } from "../baseAiGenProvider.service";
|
|
2
|
+
import { CreditUsageParams, UpscaleImageParams, UpscaleImageStatusResult, UpscaleStatusParams, UpscaleSubmitResult, UpscaleVideoParams, UpscaleVideoStatusResult } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Topaz Labs upscale provider (image + video). Both APIs are async — submit
|
|
5
|
+
* returns a provider request id, the caller (queue processor) polls the
|
|
6
|
+
* matching `checkUpscale*Status` method until a terminal state. On success
|
|
7
|
+
* the status method has ALREADY rehosted Topaz's time-limited result URL into
|
|
8
|
+
* our own storage (mediaStorage) — never the provider's own temp URL, same
|
|
9
|
+
* "always control our own copy" convention as every other provider
|
|
10
|
+
* (see runway.service.ts checkVideoStatus).
|
|
11
|
+
*/
|
|
12
|
+
export declare class TopazService extends BaseAiGenProviderService {
|
|
13
|
+
private readonly apiKey;
|
|
14
|
+
constructor();
|
|
15
|
+
/**
|
|
16
|
+
* POST /enhance/async (multipart). Topaz needs the actual image bytes, so
|
|
17
|
+
* we download the caller's `inputImageUrl` first rather than passing the
|
|
18
|
+
* URL through — the input is already our own stored asset (from
|
|
19
|
+
* /v1/media/single-upload) by the time this runs.
|
|
20
|
+
*/
|
|
21
|
+
upscaleImage(params: UpscaleImageParams): Promise<UpscaleSubmitResult>;
|
|
22
|
+
/**
|
|
23
|
+
* GET /status/{id} then, on completion, GET /download/{id} for a temporary
|
|
24
|
+
* URL — which we immediately rehost into mediaStorage. Never return
|
|
25
|
+
* Topaz's own URL as the job's final `outputUrl`.
|
|
26
|
+
*/
|
|
27
|
+
checkUpscaleImageStatus({ providerRequestId, outputFilename, outputFilePath, }: UpscaleStatusParams): Promise<UpscaleImageStatusResult>;
|
|
28
|
+
/**
|
|
29
|
+
* Drives Topaz's full submit -> accept -> upload -> complete-upload
|
|
30
|
+
* sequence in one call so the queue processor only has to poll afterwards.
|
|
31
|
+
* Splitting this across separate BullMQ steps would multiply the ways a job
|
|
32
|
+
* can get stuck between retries with no clean resume point — Topaz's own
|
|
33
|
+
* `status` polling is the one resumable point post-submit.
|
|
34
|
+
*/
|
|
35
|
+
upscaleVideo(params: UpscaleVideoParams): Promise<UpscaleSubmitResult>;
|
|
36
|
+
/**
|
|
37
|
+
* GET /video/{id}/status. On completion, downloads the returned link and
|
|
38
|
+
* rehosts it into mediaStorage — same "never persist the provider's own
|
|
39
|
+
* temp URL" rule as the image path above.
|
|
40
|
+
*/
|
|
41
|
+
checkUpscaleVideoStatus({ providerRequestId, outputFilename, outputFilePath, }: UpscaleStatusParams): Promise<UpscaleVideoStatusResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Image: priced per output megapixel (`cost.perMegapixel`), floored at
|
|
44
|
+
* MIN_CREDITS_PER_IMAGE_OP. Video: `cost.perSecond` is an UNVERIFIED
|
|
45
|
+
* placeholder (Topaz doesn't publish a video pricing formula — see
|
|
46
|
+
* topaz.ts module docblock) floored at the standard paid-op floor until the
|
|
47
|
+
* real rate is confirmed.
|
|
48
|
+
*/
|
|
49
|
+
getCreditUsed({ modelKey, outputMegapixels, duration, creditFloor }: CreditUsageParams): number;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=topaz.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topaz.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/topaz/topaz.service.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACzB,MAAM,UAAU,CAAC;AA4BlB;;;;;;;;GAQG;AACH,qBAAa,YAAa,SAAQ,wBAAwB;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAchC;;;;;OAKG;IACG,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAkD5E;;;;OAIG;IACG,uBAAuB,CAAC,EAC5B,iBAAiB,EACjB,cAAc,EACd,cAAiC,GAClC,EAAE,mBAAmB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IA2C1D;;;;;;OAMG;IACG,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA6I5E;;;;OAIG;IACG,uBAAuB,CAAC,EAC5B,iBAAiB,EACjB,cAAc,EACd,cAAiC,GAClC,EAAE,mBAAmB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAyC1D;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAY,EAAE,WAAW,EAAE,EAAE,iBAAiB,GAAG,MAAM;CAmBpG"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TopazService = void 0;
|
|
4
|
+
const aiModels_1 = require("../../../../globals/aiModels");
|
|
5
|
+
const creditUnit_1 = require("../../../../globals/creditUnit");
|
|
6
|
+
const storage_1 = require("../../../../libs/storage");
|
|
7
|
+
const errors_1 = require("../../../../utils/errors");
|
|
8
|
+
const helpers_1 = require("../../../../utils/helpers");
|
|
9
|
+
const logger_1 = require("../../../../utils/logger");
|
|
10
|
+
const transientRetry_1 = require("../../transientRetry");
|
|
11
|
+
const baseAiGenProvider_service_1 = require("../baseAiGenProvider.service");
|
|
12
|
+
const IMAGE_BASE_URL = "https://api.topazlabs.com/image/v1";
|
|
13
|
+
const VIDEO_BASE_URL = "https://api.topazlabs.com/video";
|
|
14
|
+
/**
|
|
15
|
+
* Classify a Topaz HTTP failure (non-2xx). Same shape as every other
|
|
16
|
+
* provider's classifier (see pixverse.service.ts's classifyPixVerseHttpError)
|
|
17
|
+
* — 401/403 is a deploy/config bug (fail fast, no Slack noise per job), 5xx/429
|
|
18
|
+
* is transient and retried by withTransientRetry, everything else is a real
|
|
19
|
+
* bug worth alerting on.
|
|
20
|
+
*/
|
|
21
|
+
function classifyTopazHttpError(status, body, op) {
|
|
22
|
+
if (status === 401 || status === 403) {
|
|
23
|
+
return new errors_1.UserFacingError(`Topaz rejected the API key (HTTP ${status}). Ask an operator to verify TOPAZ_API_KEY.`, errors_1.USER_FACING_ERROR_CODES.PROVIDER_AUTH_ERROR);
|
|
24
|
+
}
|
|
25
|
+
if (status >= 500 || status === 429) {
|
|
26
|
+
return new transientRetry_1.TransientHttpError(status, `Topaz ${op} transient HTTP ${status}: ${body.slice(0, 200)}`);
|
|
27
|
+
}
|
|
28
|
+
return new Error(`Topaz ${op} failed (${status}): ${body}`);
|
|
29
|
+
}
|
|
30
|
+
const IMAGE_TERMINAL_COMPLETED = ["completed", "complete", "succeeded", "success"];
|
|
31
|
+
const VIDEO_TERMINAL_COMPLETED = ["completed", "complete", "succeeded", "success"];
|
|
32
|
+
/**
|
|
33
|
+
* Topaz Labs upscale provider (image + video). Both APIs are async — submit
|
|
34
|
+
* returns a provider request id, the caller (queue processor) polls the
|
|
35
|
+
* matching `checkUpscale*Status` method until a terminal state. On success
|
|
36
|
+
* the status method has ALREADY rehosted Topaz's time-limited result URL into
|
|
37
|
+
* our own storage (mediaStorage) — never the provider's own temp URL, same
|
|
38
|
+
* "always control our own copy" convention as every other provider
|
|
39
|
+
* (see runway.service.ts checkVideoStatus).
|
|
40
|
+
*/
|
|
41
|
+
class TopazService extends baseAiGenProvider_service_1.BaseAiGenProviderService {
|
|
42
|
+
constructor() {
|
|
43
|
+
super();
|
|
44
|
+
if (!process.env.TOPAZ_API_KEY) {
|
|
45
|
+
throw new Error("Missing TOPAZ_API_KEY in environment variables");
|
|
46
|
+
}
|
|
47
|
+
this.apiKey = process.env.TOPAZ_API_KEY;
|
|
48
|
+
}
|
|
49
|
+
// ============================================================
|
|
50
|
+
// IMAGE UPSCALE
|
|
51
|
+
// ============================================================
|
|
52
|
+
/**
|
|
53
|
+
* POST /enhance/async (multipart). Topaz needs the actual image bytes, so
|
|
54
|
+
* we download the caller's `inputImageUrl` first rather than passing the
|
|
55
|
+
* URL through — the input is already our own stored asset (from
|
|
56
|
+
* /v1/media/single-upload) by the time this runs.
|
|
57
|
+
*/
|
|
58
|
+
async upscaleImage(params) {
|
|
59
|
+
const modelId = aiModels_1.aiModelConfigs[params.modelKey]?.modelId;
|
|
60
|
+
if (!modelId) {
|
|
61
|
+
throw new Error(`Unknown Topaz image modelKey: ${params.modelKey}`);
|
|
62
|
+
}
|
|
63
|
+
const srcResp = await fetch(params.inputImageUrl);
|
|
64
|
+
if (!srcResp.ok) {
|
|
65
|
+
throw new errors_1.UserFacingError("Your input image could not be read for upscaling. Please try uploading it again.", errors_1.USER_FACING_ERROR_CODES.INPUT_IMAGE_REJECTED);
|
|
66
|
+
}
|
|
67
|
+
const contentType = srcResp.headers.get("content-type") || "image/jpeg";
|
|
68
|
+
const imageBuffer = Buffer.from(await srcResp.arrayBuffer());
|
|
69
|
+
const form = new FormData();
|
|
70
|
+
form.append("model", modelId);
|
|
71
|
+
form.append("output_format", params.outputFormat ?? "jpeg");
|
|
72
|
+
form.append("image", new Blob([imageBuffer], { type: contentType }), "input");
|
|
73
|
+
return (0, transientRetry_1.withTransientRetry)("topaz:upscaleImage", async () => {
|
|
74
|
+
const resp = await fetch(`${IMAGE_BASE_URL}/enhance/async`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: { "X-API-KEY": this.apiKey },
|
|
77
|
+
body: form,
|
|
78
|
+
});
|
|
79
|
+
if (!resp.ok) {
|
|
80
|
+
throw classifyTopazHttpError(resp.status, await resp.text(), "upscaleImage");
|
|
81
|
+
}
|
|
82
|
+
const data = (await resp.json());
|
|
83
|
+
if (!data.process_id) {
|
|
84
|
+
throw new Error("Topaz image upscale did not return a process_id");
|
|
85
|
+
}
|
|
86
|
+
return { providerRequestId: data.process_id };
|
|
87
|
+
}, {
|
|
88
|
+
onRetry: ({ attempt, maxAttempts, backoffMs, err }) => logger_1.logger.warn("Topaz upscaleImage transient error — retrying", {
|
|
89
|
+
attempt,
|
|
90
|
+
maxAttempts,
|
|
91
|
+
backoffMs,
|
|
92
|
+
message: err.message,
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* GET /status/{id} then, on completion, GET /download/{id} for a temporary
|
|
98
|
+
* URL — which we immediately rehost into mediaStorage. Never return
|
|
99
|
+
* Topaz's own URL as the job's final `outputUrl`.
|
|
100
|
+
*/
|
|
101
|
+
async checkUpscaleImageStatus({ providerRequestId, outputFilename, outputFilePath = "upscale_images", }) {
|
|
102
|
+
const statusResp = await fetch(`${IMAGE_BASE_URL}/status/${providerRequestId}`, {
|
|
103
|
+
headers: { "X-API-KEY": this.apiKey },
|
|
104
|
+
});
|
|
105
|
+
if (!statusResp.ok) {
|
|
106
|
+
throw classifyTopazHttpError(statusResp.status, await statusResp.text(), "checkUpscaleImageStatus");
|
|
107
|
+
}
|
|
108
|
+
const statusData = (await statusResp.json());
|
|
109
|
+
const normalized = (statusData.status ?? "").toLowerCase();
|
|
110
|
+
if (normalized === "failed" || normalized === "error") {
|
|
111
|
+
return { status: "failed", errorMessage: statusData.error ?? "Topaz image upscale failed" };
|
|
112
|
+
}
|
|
113
|
+
if (!IMAGE_TERMINAL_COMPLETED.includes(normalized)) {
|
|
114
|
+
return { status: "processing" };
|
|
115
|
+
}
|
|
116
|
+
const downloadResp = await fetch(`${IMAGE_BASE_URL}/download/${providerRequestId}`, {
|
|
117
|
+
headers: { "X-API-KEY": this.apiKey },
|
|
118
|
+
});
|
|
119
|
+
if (!downloadResp.ok) {
|
|
120
|
+
throw classifyTopazHttpError(downloadResp.status, await downloadResp.text(), "checkUpscaleImageStatus:download");
|
|
121
|
+
}
|
|
122
|
+
const { url } = (await downloadResp.json());
|
|
123
|
+
if (!url)
|
|
124
|
+
throw new Error("Topaz image download endpoint did not return a url");
|
|
125
|
+
const assetResp = await fetch(url);
|
|
126
|
+
if (!assetResp.ok) {
|
|
127
|
+
throw new Error(`Failed to download Topaz upscaled image: ${assetResp.status}`);
|
|
128
|
+
}
|
|
129
|
+
const contentType = assetResp.headers.get("content-type") || "image/jpeg";
|
|
130
|
+
const ext = (contentType.split("/")[1] || "jpeg").toLowerCase();
|
|
131
|
+
const buffer = Buffer.from(await assetResp.arrayBuffer());
|
|
132
|
+
// mediaStorage: GCS write + best-effort R2 mirror (zero-egress delivery).
|
|
133
|
+
const signedUrl = await storage_1.mediaStorage.put(`${outputFilePath}/${outputFilename}.${ext}`, buffer, contentType);
|
|
134
|
+
return { status: "completed", outputUrl: signedUrl };
|
|
135
|
+
}
|
|
136
|
+
// ============================================================
|
|
137
|
+
// VIDEO UPSCALE
|
|
138
|
+
// ============================================================
|
|
139
|
+
/**
|
|
140
|
+
* Drives Topaz's full submit -> accept -> upload -> complete-upload
|
|
141
|
+
* sequence in one call so the queue processor only has to poll afterwards.
|
|
142
|
+
* Splitting this across separate BullMQ steps would multiply the ways a job
|
|
143
|
+
* can get stuck between retries with no clean resume point — Topaz's own
|
|
144
|
+
* `status` polling is the one resumable point post-submit.
|
|
145
|
+
*/
|
|
146
|
+
async upscaleVideo(params) {
|
|
147
|
+
const modelId = aiModels_1.aiModelConfigs[params.modelKey]?.modelId;
|
|
148
|
+
if (!modelId) {
|
|
149
|
+
throw new Error(`Unknown Topaz video modelKey: ${params.modelKey}`);
|
|
150
|
+
}
|
|
151
|
+
const { source } = params;
|
|
152
|
+
const requestId = await (0, transientRetry_1.withTransientRetry)("topaz:upscaleVideo:submit", async () => {
|
|
153
|
+
const resp = await fetch(`${VIDEO_BASE_URL}/`, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: { "X-API-Key": this.apiKey, "Content-Type": "application/json" },
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
source: {
|
|
158
|
+
container: source.container,
|
|
159
|
+
size: source.size,
|
|
160
|
+
resolution: source.resolution,
|
|
161
|
+
frameRate: source.frameRate,
|
|
162
|
+
frameCount: source.frameCount,
|
|
163
|
+
duration: source.duration,
|
|
164
|
+
},
|
|
165
|
+
output: {
|
|
166
|
+
resolution: {
|
|
167
|
+
width: source.resolution.width * params.scale,
|
|
168
|
+
height: source.resolution.height * params.scale,
|
|
169
|
+
},
|
|
170
|
+
frameRate: source.frameRate,
|
|
171
|
+
container: source.container,
|
|
172
|
+
},
|
|
173
|
+
filters: [{ model: modelId, type: "upscale" }],
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
if (!resp.ok) {
|
|
177
|
+
throw classifyTopazHttpError(resp.status, await resp.text(), "upscaleVideo:submit");
|
|
178
|
+
}
|
|
179
|
+
const data = (await resp.json());
|
|
180
|
+
const id = data.id ?? data.requestId;
|
|
181
|
+
if (!id)
|
|
182
|
+
throw new Error("Topaz video submit did not return a request id");
|
|
183
|
+
return id;
|
|
184
|
+
}, {
|
|
185
|
+
onRetry: ({ attempt, maxAttempts, backoffMs, err }) => logger_1.logger.warn("Topaz upscaleVideo submit transient error — retrying", {
|
|
186
|
+
attempt,
|
|
187
|
+
maxAttempts,
|
|
188
|
+
backoffMs,
|
|
189
|
+
message: err.message,
|
|
190
|
+
}),
|
|
191
|
+
});
|
|
192
|
+
const uploadUrl = await (0, transientRetry_1.withTransientRetry)("topaz:upscaleVideo:accept", async () => {
|
|
193
|
+
const resp = await fetch(`${VIDEO_BASE_URL}/${requestId}/accept`, {
|
|
194
|
+
method: "PATCH",
|
|
195
|
+
headers: { "X-API-Key": this.apiKey },
|
|
196
|
+
});
|
|
197
|
+
if (!resp.ok) {
|
|
198
|
+
throw classifyTopazHttpError(resp.status, await resp.text(), "upscaleVideo:accept");
|
|
199
|
+
}
|
|
200
|
+
const data = (await resp.json());
|
|
201
|
+
const url = data.url ?? data.urls?.[0];
|
|
202
|
+
if (!url)
|
|
203
|
+
throw new Error("Topaz video accept did not return an upload url");
|
|
204
|
+
return url;
|
|
205
|
+
}, {
|
|
206
|
+
onRetry: ({ attempt, maxAttempts, backoffMs, err }) => logger_1.logger.warn("Topaz upscaleVideo accept transient error — retrying", {
|
|
207
|
+
attempt,
|
|
208
|
+
maxAttempts,
|
|
209
|
+
backoffMs,
|
|
210
|
+
message: err.message,
|
|
211
|
+
}),
|
|
212
|
+
});
|
|
213
|
+
// Our own already-persisted source (from /v1/media/single-upload) — never
|
|
214
|
+
// a browser upload stream directly.
|
|
215
|
+
const srcResp = await fetch(params.inputVideoUrl);
|
|
216
|
+
if (!srcResp.ok) {
|
|
217
|
+
throw new errors_1.UserFacingError("Your input video could not be read for upscaling. Please try uploading it again.", errors_1.USER_FACING_ERROR_CODES.INPUT_IMAGE_REJECTED);
|
|
218
|
+
}
|
|
219
|
+
const videoBuffer = Buffer.from(await srcResp.arrayBuffer());
|
|
220
|
+
const eTag = await (0, transientRetry_1.withTransientRetry)("topaz:upscaleVideo:upload", async () => {
|
|
221
|
+
const putResp = await fetch(uploadUrl, {
|
|
222
|
+
method: "PUT",
|
|
223
|
+
headers: { "Content-Type": `video/${source.container}` },
|
|
224
|
+
body: videoBuffer,
|
|
225
|
+
});
|
|
226
|
+
if (!putResp.ok) {
|
|
227
|
+
throw classifyTopazHttpError(putResp.status, await putResp.text(), "upscaleVideo:upload");
|
|
228
|
+
}
|
|
229
|
+
const tag = putResp.headers.get("etag");
|
|
230
|
+
if (!tag)
|
|
231
|
+
throw new Error("Topaz video upload did not return an ETag");
|
|
232
|
+
return tag;
|
|
233
|
+
}, {
|
|
234
|
+
onRetry: ({ attempt, maxAttempts, backoffMs, err }) => logger_1.logger.warn("Topaz upscaleVideo upload transient error — retrying", {
|
|
235
|
+
attempt,
|
|
236
|
+
maxAttempts,
|
|
237
|
+
backoffMs,
|
|
238
|
+
message: err.message,
|
|
239
|
+
}),
|
|
240
|
+
});
|
|
241
|
+
await (0, transientRetry_1.withTransientRetry)("topaz:upscaleVideo:complete", async () => {
|
|
242
|
+
const resp = await fetch(`${VIDEO_BASE_URL}/${requestId}/complete-upload`, {
|
|
243
|
+
method: "PATCH",
|
|
244
|
+
headers: { "X-API-Key": this.apiKey, "Content-Type": "application/json" },
|
|
245
|
+
body: JSON.stringify({ eTag }),
|
|
246
|
+
});
|
|
247
|
+
if (!resp.ok) {
|
|
248
|
+
throw classifyTopazHttpError(resp.status, await resp.text(), "upscaleVideo:complete");
|
|
249
|
+
}
|
|
250
|
+
}, {
|
|
251
|
+
onRetry: ({ attempt, maxAttempts, backoffMs, err }) => logger_1.logger.warn("Topaz upscaleVideo complete-upload transient error — retrying", {
|
|
252
|
+
attempt,
|
|
253
|
+
maxAttempts,
|
|
254
|
+
backoffMs,
|
|
255
|
+
message: err.message,
|
|
256
|
+
}),
|
|
257
|
+
});
|
|
258
|
+
return { providerRequestId: requestId };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* GET /video/{id}/status. On completion, downloads the returned link and
|
|
262
|
+
* rehosts it into mediaStorage — same "never persist the provider's own
|
|
263
|
+
* temp URL" rule as the image path above.
|
|
264
|
+
*/
|
|
265
|
+
async checkUpscaleVideoStatus({ providerRequestId, outputFilename, outputFilePath = "upscale_videos", }) {
|
|
266
|
+
const resp = await fetch(`${VIDEO_BASE_URL}/${providerRequestId}/status`, {
|
|
267
|
+
headers: { "X-API-Key": this.apiKey },
|
|
268
|
+
});
|
|
269
|
+
if (!resp.ok) {
|
|
270
|
+
throw classifyTopazHttpError(resp.status, await resp.text(), "checkUpscaleVideoStatus");
|
|
271
|
+
}
|
|
272
|
+
const data = (await resp.json());
|
|
273
|
+
const normalized = (data.status ?? "").toLowerCase();
|
|
274
|
+
if (normalized === "failed" || normalized === "error") {
|
|
275
|
+
return { status: "failed", errorMessage: data.error ?? "Topaz video upscale failed" };
|
|
276
|
+
}
|
|
277
|
+
if (normalized === "cancelled" || normalized === "canceled") {
|
|
278
|
+
return { status: "cancelled", errorMessage: data.error ?? "Topaz video upscale was cancelled" };
|
|
279
|
+
}
|
|
280
|
+
const downloadUrl = data.downloadUrl ?? data.download_url;
|
|
281
|
+
if (!VIDEO_TERMINAL_COMPLETED.includes(normalized) || !downloadUrl) {
|
|
282
|
+
return { status: "processing" };
|
|
283
|
+
}
|
|
284
|
+
const assetResp = await fetch(downloadUrl);
|
|
285
|
+
if (!assetResp.ok) {
|
|
286
|
+
throw new Error(`Failed to download Topaz upscaled video: ${assetResp.status}`);
|
|
287
|
+
}
|
|
288
|
+
const buffer = Buffer.from(await assetResp.arrayBuffer());
|
|
289
|
+
// mediaStorage: GCS write + best-effort R2 mirror (zero-egress delivery).
|
|
290
|
+
const signedUrl = await storage_1.mediaStorage.put(`${outputFilePath}/${outputFilename}.mp4`, buffer, "video/mp4");
|
|
291
|
+
return { status: "completed", outputUrl: signedUrl };
|
|
292
|
+
}
|
|
293
|
+
// ============================================================
|
|
294
|
+
// CREDITS
|
|
295
|
+
// ============================================================
|
|
296
|
+
/**
|
|
297
|
+
* Image: priced per output megapixel (`cost.perMegapixel`), floored at
|
|
298
|
+
* MIN_CREDITS_PER_IMAGE_OP. Video: `cost.perSecond` is an UNVERIFIED
|
|
299
|
+
* placeholder (Topaz doesn't publish a video pricing formula — see
|
|
300
|
+
* topaz.ts module docblock) floored at the standard paid-op floor until the
|
|
301
|
+
* real rate is confirmed.
|
|
302
|
+
*/
|
|
303
|
+
getCreditUsed({ modelKey, outputMegapixels, duration = 5, creditFloor }) {
|
|
304
|
+
const modelConfig = aiModels_1.aiModelConfigs[modelKey];
|
|
305
|
+
const cost = modelConfig?.cost;
|
|
306
|
+
if (cost?.perMegapixel !== undefined) {
|
|
307
|
+
// Fall back to a conservative 24MP (Topaz's 1-credit tier boundary) if
|
|
308
|
+
// the caller didn't compute an exact output size.
|
|
309
|
+
const megapixels = outputMegapixels ?? 24;
|
|
310
|
+
return (0, helpers_1.getCreditsFromCost)(cost.perMegapixel * megapixels, true, creditFloor ?? creditUnit_1.MIN_CREDITS_PER_IMAGE_OP);
|
|
311
|
+
}
|
|
312
|
+
if (cost?.perSecond !== undefined) {
|
|
313
|
+
return (0, helpers_1.getCreditsFromCost)(cost.perSecond * duration, true, creditFloor);
|
|
314
|
+
}
|
|
315
|
+
logger_1.logger.warn(`Topaz getCreditUsed: no cost config for modelKey="${modelKey}" — returning fallback`, {
|
|
316
|
+
modelKey,
|
|
317
|
+
});
|
|
318
|
+
return (0, helpers_1.getCreditsFromCost)(1.0, true, creditFloor);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
exports.TopazService = TopazService;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EImageGenModelKey, EMusicGenModels, ESfxGenModels, ETextGenModels, EVideoGenModels } from "../../../globals/aiModels";
|
|
1
|
+
import { EImageGenModelKey, EMusicGenModels, ESfxGenModels, ETextGenModels, EUpscaleImageModelKey, EUpscaleVideoModelKey, EVideoGenModels } from "../../../globals/aiModels";
|
|
2
2
|
import { EVideoSceneStatus, TVideoJobTask } from "../../../globals/types";
|
|
3
3
|
import { EVideoMode } from "../types";
|
|
4
4
|
import type { CameraControl } from "../canonicalAdapters/cameraControl.types";
|
|
@@ -304,8 +304,73 @@ export interface SfxGenerationResult {
|
|
|
304
304
|
mimeType: string;
|
|
305
305
|
extension: string;
|
|
306
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Image upscale (Topaz). Async — the provider returns a `providerRequestId`
|
|
309
|
+
* immediately; the caller polls `checkUpscaleImageStatus()` until the status
|
|
310
|
+
* is terminal. On success the result's `outputUrl` is already persisted into
|
|
311
|
+
* OUR storage (mediaStorage) — never the provider's own temporary URL, same
|
|
312
|
+
* convention as `VideoStatusResult.videoUrl`.
|
|
313
|
+
*/
|
|
314
|
+
export interface UpscaleImageParams {
|
|
315
|
+
modelKey: EUpscaleImageModelKey;
|
|
316
|
+
inputImageUrl: string;
|
|
317
|
+
/** 2 or 4 — see topaz.ts model config `fields.scale`. */
|
|
318
|
+
scale: number;
|
|
319
|
+
outputFormat?: "jpeg" | "png";
|
|
320
|
+
}
|
|
321
|
+
export interface UpscaleSubmitResult {
|
|
322
|
+
providerRequestId: string;
|
|
323
|
+
}
|
|
324
|
+
export type TUpscaleProviderStatus = "processing" | "completed" | "failed" | "cancelled";
|
|
325
|
+
export interface UpscaleStatusParams {
|
|
326
|
+
providerRequestId: string;
|
|
327
|
+
/** Object-storage filename (no extension) the result is persisted under on completion. */
|
|
328
|
+
outputFilename: string;
|
|
329
|
+
/** Object-storage folder. Defaults to "upscale_images" / "upscale_videos" per provider. */
|
|
330
|
+
outputFilePath?: string;
|
|
331
|
+
}
|
|
332
|
+
export interface UpscaleImageStatusResult {
|
|
333
|
+
status: TUpscaleProviderStatus;
|
|
334
|
+
/** Present once status is "completed" — already a mediaStorage-signed URL. */
|
|
335
|
+
outputUrl?: string;
|
|
336
|
+
width?: number;
|
|
337
|
+
height?: number;
|
|
338
|
+
errorMessage?: string;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Video upscale (Topaz). Multi-step: submit → accept (upload URL) → upload
|
|
342
|
+
* source bytes → complete-upload → poll. `upscaleVideo()` drives the whole
|
|
343
|
+
* submit-through-complete-upload sequence and returns the request id;
|
|
344
|
+
* `checkUpscaleVideoStatus()` polls from there.
|
|
345
|
+
*/
|
|
346
|
+
export interface UpscaleVideoParams {
|
|
347
|
+
modelKey: EUpscaleVideoModelKey;
|
|
348
|
+
inputVideoUrl: string;
|
|
349
|
+
scale: number;
|
|
350
|
+
source: {
|
|
351
|
+
resolution: {
|
|
352
|
+
width: number;
|
|
353
|
+
height: number;
|
|
354
|
+
};
|
|
355
|
+
container: string;
|
|
356
|
+
/** Bytes. */
|
|
357
|
+
size: number;
|
|
358
|
+
/** Seconds. */
|
|
359
|
+
duration: number;
|
|
360
|
+
frameRate: number;
|
|
361
|
+
frameCount: number;
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
export interface UpscaleVideoStatusResult {
|
|
365
|
+
status: TUpscaleProviderStatus;
|
|
366
|
+
outputUrl?: string;
|
|
367
|
+
width?: number;
|
|
368
|
+
height?: number;
|
|
369
|
+
durationSec?: number;
|
|
370
|
+
errorMessage?: string;
|
|
371
|
+
}
|
|
307
372
|
export interface CreditUsageParams {
|
|
308
|
-
modelKey: EVideoGenModels | ETextGenModels | EImageGenModelKey | EMusicGenModels | ESfxGenModels;
|
|
373
|
+
modelKey: EVideoGenModels | ETextGenModels | EImageGenModelKey | EMusicGenModels | ESfxGenModels | EUpscaleImageModelKey | EUpscaleVideoModelKey;
|
|
309
374
|
mode?: EVideoMode;
|
|
310
375
|
duration?: number;
|
|
311
376
|
resolution?: string;
|
|
@@ -338,5 +403,11 @@ export interface CreditUsageParams {
|
|
|
338
403
|
* MIN_CREDITS_PER_PAID_OP. See globals/creditUnit.ts.
|
|
339
404
|
*/
|
|
340
405
|
creditFloor?: number;
|
|
406
|
+
/**
|
|
407
|
+
* Topaz image upscale — output megapixels (post-scale width*height/1e6),
|
|
408
|
+
* priced per-megapixel (see topaz.ts `cost.perMegapixel`). Left undefined
|
|
409
|
+
* for every other modality.
|
|
410
|
+
*/
|
|
411
|
+
outputMegapixels?: number;
|
|
341
412
|
}
|
|
342
413
|
//# sourceMappingURL=types.d.ts.map
|