@brotu/ai 0.5.0 → 0.6.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/CATALOG.md +298 -4
- package/README.md +5 -4
- package/dist/adapters/topaz.adapter.d.ts +71 -0
- package/dist/adapters/topaz.adapter.d.ts.map +1 -0
- package/dist/catalog.cjs +102 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +1 -1
- package/dist/{chunk-3MC3RDAJ.js → chunk-QQIHMDJC.js} +107 -2
- package/dist/client.d.ts +12 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/index.cjs +674 -32
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +544 -4
- package/dist/ports/content-generator.port.d.ts +12 -0
- package/dist/ports/content-generator.port.d.ts.map +1 -1
- package/dist/providers/topaz.models.d.ts +24 -0
- package/dist/providers/topaz.models.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -62,6 +62,11 @@ __export(index_exports, {
|
|
|
62
62
|
QWEN_TEXT_MODELS: () => QWEN_TEXT_MODELS,
|
|
63
63
|
QWEN_VIDEO_MODELS: () => QWEN_VIDEO_MODELS,
|
|
64
64
|
QwenAdapter: () => QwenAdapter,
|
|
65
|
+
TOPAZ_CATALOG: () => TOPAZ_CATALOG,
|
|
66
|
+
TOPAZ_IMAGE_MODELS: () => TOPAZ_IMAGE_MODELS,
|
|
67
|
+
TOPAZ_MODELS: () => TOPAZ_MODELS,
|
|
68
|
+
TOPAZ_VIDEO_MODELS: () => TOPAZ_VIDEO_MODELS,
|
|
69
|
+
TopazAdapter: () => TopazAdapter,
|
|
65
70
|
brotu: () => brotu,
|
|
66
71
|
createS3Storage: () => createS3Storage,
|
|
67
72
|
deliverWebhook: () => deliverWebhook,
|
|
@@ -834,7 +839,7 @@ var imageOmni = {
|
|
|
834
839
|
prompt: input.prompt
|
|
835
840
|
};
|
|
836
841
|
if (images.length > 0) {
|
|
837
|
-
body.image_list = images.map((
|
|
842
|
+
body.image_list = images.map((image2) => ({ image: image2 }));
|
|
838
843
|
}
|
|
839
844
|
if (elements.length > 0) {
|
|
840
845
|
body.element_list = elements.map((id) => ({ element_id: id }));
|
|
@@ -1236,6 +1241,105 @@ var QWEN_CATALOG = [
|
|
|
1236
1241
|
)
|
|
1237
1242
|
];
|
|
1238
1243
|
|
|
1244
|
+
// src/providers/topaz.models.ts
|
|
1245
|
+
var VIDEO_RES = ["720p", "1080p", "4k"];
|
|
1246
|
+
function video(id, name, kind, description) {
|
|
1247
|
+
return [id, { name, vendorModel: id, surface: "video", kind, description }];
|
|
1248
|
+
}
|
|
1249
|
+
function image(id, name, vendorModel, imagePath, description) {
|
|
1250
|
+
return [
|
|
1251
|
+
id,
|
|
1252
|
+
{ name, vendorModel, surface: "image", imagePath, description }
|
|
1253
|
+
];
|
|
1254
|
+
}
|
|
1255
|
+
var ENTRIES = [
|
|
1256
|
+
// Precision video upscale (Proteus family)
|
|
1257
|
+
video("prob-4", "Proteus", "upscale", "Default video upscale. Preserves the source."),
|
|
1258
|
+
video("pnat-1", "Proteus Natural", "upscale", "Softer Proteus. 2\xD7 only."),
|
|
1259
|
+
video("rhea-1", "Rhea", "upscale", "High-fidelity 4\xD7 upscale for fine detail."),
|
|
1260
|
+
video("thd-3", "Theia Fine Tune Detail", "upscale", "More aggressive detail."),
|
|
1261
|
+
video("thf-4", "Theia Fine Tune Fidelity", "upscale", "Lighter touch, closer to the source."),
|
|
1262
|
+
video("thm-2", "Themis 2", "upscale", "Motion deblur."),
|
|
1263
|
+
video("aaa-9", "Artemis Aliasing / Moire", "upscale", "Aliasing and moire cleanup."),
|
|
1264
|
+
video("ahq-12", "Artemis High Quality", "upscale", "Artemis for clean footage."),
|
|
1265
|
+
video("amq-13", "Artemis Medium Quality", "upscale", "Artemis for typical compression."),
|
|
1266
|
+
video("amqs-2", "Artemis Medium Halo", "upscale", "Medium halo reduction."),
|
|
1267
|
+
video("alq-13", "Artemis Low Quality", "upscale", "Artemis for degraded sources."),
|
|
1268
|
+
video("alqs-2", "Artemis Strong Halo", "upscale", "Strong halo reduction."),
|
|
1269
|
+
video("iris-3", "Iris Medium Quality", "upscale", "Face recovery, medium quality."),
|
|
1270
|
+
video("iris-2", "Iris Low Quality", "upscale", "Face recovery, low quality."),
|
|
1271
|
+
video("dtv-4", "Dione TV", "upscale", "Deinterlace broadcast footage."),
|
|
1272
|
+
video("ddv-3", "Dione DV", "upscale", "Deinterlace camcorder footage."),
|
|
1273
|
+
video("dtvs-2", "Dione Robust", "upscale", "Deinterlace degraded interlaced sources."),
|
|
1274
|
+
video("dtd-4", "Dione Dehalo", "upscale", "Deinterlace with halo cleanup."),
|
|
1275
|
+
video("dtds-2", "Dione Robust Dehalo", "upscale", "Deinterlace, degraded, with halo cleanup."),
|
|
1276
|
+
video("ghq-5", "Gaia High Quality", "upscale", "CGI and animation, high quality."),
|
|
1277
|
+
video("gcg-5", "Gaia CG", "upscale", "General CGI and renders."),
|
|
1278
|
+
video("nyx-3", "Nyx", "upscale", "Default video denoise."),
|
|
1279
|
+
video("nxl-1", "Nyx XL", "upscale", "Heavy video denoise."),
|
|
1280
|
+
video("nxhf-1", "Nyx High Fidelity", "upscale", "Light denoise, keep texture."),
|
|
1281
|
+
video("nxf-1", "Nyx Fast", "upscale", "Fast video denoise."),
|
|
1282
|
+
video("color-1", "Video Colorization", "upscale", "Black-and-white to color."),
|
|
1283
|
+
// Generative / creative video
|
|
1284
|
+
video("slp-2.6", "Starlight Precise 2.6", "upscale", "Generative upscale to 4K. Faces, texture, text."),
|
|
1285
|
+
video("slhq-1", "Starlight HQ", "upscale", "Maximum quality generative enhance."),
|
|
1286
|
+
video("slf-2", "Starlight Fast 2", "upscale", "Faster generative upscale."),
|
|
1287
|
+
video("slm-1", "Starlight Mini", "upscale", "Archival and interlaced restoration."),
|
|
1288
|
+
video("wonder-1", "Starlight Sharp", "upscale", "Local-first sharpening restore."),
|
|
1289
|
+
video("ast-2", "Astra 2", "upscale", "Creative GenAI video upscale."),
|
|
1290
|
+
// Frame interpolation — not upscalers
|
|
1291
|
+
video("aion-1", "Aion", "interpolation", "Extreme slow motion on complex, high-res motion."),
|
|
1292
|
+
video("apo-8", "Apollo", "interpolation", "Default slow-mo and frame-rate conversion."),
|
|
1293
|
+
video("apf-2", "Apollo Fast", "interpolation", "Faster Apollo."),
|
|
1294
|
+
video("chr-2", "Chronos", "interpolation", "Interpolation for harder motion."),
|
|
1295
|
+
video("chf-3", "Chronos Fast", "interpolation", "Faster Chronos."),
|
|
1296
|
+
// Image — Gigapixel / enhance
|
|
1297
|
+
image("standard-v2", "Standard 2", "Standard V2", "/image/v1/enhance/async", "Default image upscale."),
|
|
1298
|
+
image("high-fidelity-v2", "High Fidelity 2", "High Fidelity V2", "/image/v1/enhance/async", "Maximum source preservation."),
|
|
1299
|
+
image("low-resolution-v2", "Low Resolution 2", "Low Resolution V2", "/image/v1/enhance/async", "Tiny or heavily compressed sources."),
|
|
1300
|
+
image("cgi", "Art & CGI", "CGI", "/image/v1/enhance/async", "Illustrations, renders, game art."),
|
|
1301
|
+
image("text-refine", "Text & Shapes", "Text Refine", "/image/v1/enhance/async", "Documents, screenshots, type."),
|
|
1302
|
+
// Image — generative enhance
|
|
1303
|
+
image("standard-max", "Standard Max", "Standard MAX", "/image/v1/enhance-gen/async", "Default generative image upscale."),
|
|
1304
|
+
image("wonder", "Wonder", "Wonder", "/image/v1/enhance-gen/async", "One-click generative restore."),
|
|
1305
|
+
image("wonder-3.5", "Wonder 3.5", "Wonder 3.5", "/image/v1/enhance-gen/async", "Highest-quality generative image upscale."),
|
|
1306
|
+
image("redefine", "Redefine", "Redefine", "/image/v1/enhance-gen/async", "Prompt-guided generative enhance."),
|
|
1307
|
+
image("recovery-v2", "Recover 3", "Recovery V2", "/image/v1/enhance-gen/async", "Heavily damaged sources."),
|
|
1308
|
+
image("bloom-2", "Bloom 2", "Bloom 2", "/image/v1/enhance-gen/async", "Creative upscale for GenAI images."),
|
|
1309
|
+
image("denoise-max", "Denoise Max", "Denoise Max", "/image/v1/denoise-gen/async", "Highest-quality image denoise.")
|
|
1310
|
+
];
|
|
1311
|
+
var TOPAZ_MODELS = Object.fromEntries(
|
|
1312
|
+
ENTRIES.flatMap(([id, binding]) => {
|
|
1313
|
+
const rows = [[id, binding]];
|
|
1314
|
+
if (binding.surface === "image" && binding.vendorModel !== id) {
|
|
1315
|
+
rows.push([binding.vendorModel, binding]);
|
|
1316
|
+
}
|
|
1317
|
+
return rows;
|
|
1318
|
+
})
|
|
1319
|
+
);
|
|
1320
|
+
function nodeTypes(binding) {
|
|
1321
|
+
if (binding.surface === "image") return ["image_upscale"];
|
|
1322
|
+
return binding.kind === "interpolation" ? ["video_gen"] : ["video_upscale"];
|
|
1323
|
+
}
|
|
1324
|
+
var TOPAZ_CATALOG = ENTRIES.map(([id, binding]) => ({
|
|
1325
|
+
id,
|
|
1326
|
+
name: binding.name,
|
|
1327
|
+
category: binding.surface,
|
|
1328
|
+
inputType: binding.surface === "video" ? "video_required" : "image_required",
|
|
1329
|
+
nodeTypes: nodeTypes(binding),
|
|
1330
|
+
creditsPerUnit: 0,
|
|
1331
|
+
creditUnit: binding.surface === "video" ? "second" : "image",
|
|
1332
|
+
supportedResolutions: binding.surface === "video" ? [...VIDEO_RES] : void 0,
|
|
1333
|
+
provider: "topaz",
|
|
1334
|
+
description: binding.description
|
|
1335
|
+
}));
|
|
1336
|
+
var TOPAZ_VIDEO_MODELS = Object.fromEntries(
|
|
1337
|
+
ENTRIES.filter(([, binding]) => binding.surface === "video")
|
|
1338
|
+
);
|
|
1339
|
+
var TOPAZ_IMAGE_MODELS = Object.fromEntries(
|
|
1340
|
+
ENTRIES.filter(([, binding]) => binding.surface === "image")
|
|
1341
|
+
);
|
|
1342
|
+
|
|
1239
1343
|
// src/catalog.ts
|
|
1240
1344
|
var PROVIDER_BASE_URLS = {
|
|
1241
1345
|
byteplus: "https://ark.ap-southeast.bytepluses.com",
|
|
@@ -1244,6 +1348,7 @@ var PROVIDER_BASE_URLS = {
|
|
|
1244
1348
|
google: "https://generativelanguage.googleapis.com",
|
|
1245
1349
|
openai: "https://api.openai.com",
|
|
1246
1350
|
qwen: "https://dashscope-intl.aliyuncs.com",
|
|
1351
|
+
topaz: "https://api.topazlabs.com",
|
|
1247
1352
|
brotu: "https://api.brotu.app"
|
|
1248
1353
|
};
|
|
1249
1354
|
var DEFAULT_BROTU_API_URL = PROVIDER_BASE_URLS.brotu;
|
|
@@ -1261,7 +1366,8 @@ var BUILT_IN = [
|
|
|
1261
1366
|
...QWEN_CATALOG,
|
|
1262
1367
|
...OPENAI_CATALOG,
|
|
1263
1368
|
...GOOGLE_CATALOG,
|
|
1264
|
-
...ELEVENLABS_CATALOG
|
|
1369
|
+
...ELEVENLABS_CATALOG,
|
|
1370
|
+
...TOPAZ_CATALOG
|
|
1265
1371
|
];
|
|
1266
1372
|
var catalog = BUILT_IN;
|
|
1267
1373
|
var catalogMap = byId(BUILT_IN);
|
|
@@ -1403,7 +1509,7 @@ function expiresInHours(hours) {
|
|
|
1403
1509
|
function estimateFor(provider, type, params, defaults) {
|
|
1404
1510
|
const modelId = params.model ?? "";
|
|
1405
1511
|
const model = getModel(modelId);
|
|
1406
|
-
const
|
|
1512
|
+
const video2 = params;
|
|
1407
1513
|
const unit = model?.pricing?.unit ?? (type === "video" ? "second" : type === "audio" ? "character" : type === "text" ? "token" : "image");
|
|
1408
1514
|
let units = 1;
|
|
1409
1515
|
if (unit === "character") {
|
|
@@ -1411,7 +1517,7 @@ function estimateFor(provider, type, params, defaults) {
|
|
|
1411
1517
|
} else if (unit === "token") {
|
|
1412
1518
|
units = 0;
|
|
1413
1519
|
} else if (unit === "second") {
|
|
1414
|
-
units =
|
|
1520
|
+
units = video2.duration ?? defaults?.durationSeconds ?? model?.minDuration ?? 5;
|
|
1415
1521
|
}
|
|
1416
1522
|
const resolution = params.resolution;
|
|
1417
1523
|
const rate = (resolution ? model?.pricing?.byResolution?.[resolution] : void 0) ?? model?.pricing?.usdPerUnit;
|
|
@@ -1667,23 +1773,23 @@ var BrotuAdapter = class {
|
|
|
1667
1773
|
}
|
|
1668
1774
|
try {
|
|
1669
1775
|
const workspaceId = await this.workspaceId();
|
|
1670
|
-
const
|
|
1671
|
-
const
|
|
1776
|
+
const video2 = params;
|
|
1777
|
+
const image2 = params;
|
|
1672
1778
|
const payload = await this.request(
|
|
1673
1779
|
this.studioPath("/api/v1/studio/estimate", workspaceId),
|
|
1674
1780
|
{
|
|
1675
1781
|
method: "POST",
|
|
1676
1782
|
body: JSON.stringify({
|
|
1677
1783
|
model: this.platformModel(params.model ?? ""),
|
|
1678
|
-
duration:
|
|
1679
|
-
resolution:
|
|
1680
|
-
aspectRatio:
|
|
1681
|
-
withAudio:
|
|
1784
|
+
duration: video2.duration,
|
|
1785
|
+
resolution: video2.resolution ?? image2.resolution,
|
|
1786
|
+
aspectRatio: video2.aspectRatio ?? image2.aspectRatio,
|
|
1787
|
+
withAudio: video2.withAudio,
|
|
1682
1788
|
hasReferenceImage: Boolean(
|
|
1683
|
-
|
|
1789
|
+
video2.imageUrl || video2.imageUrls?.length || video2.referenceImages?.length || image2.referenceImages?.length
|
|
1684
1790
|
),
|
|
1685
|
-
hasReferenceVideo: Boolean(
|
|
1686
|
-
quality:
|
|
1791
|
+
hasReferenceVideo: Boolean(video2.videoUrl || video2.videoUrls?.length),
|
|
1792
|
+
quality: image2.quality
|
|
1687
1793
|
})
|
|
1688
1794
|
}
|
|
1689
1795
|
);
|
|
@@ -2286,12 +2392,12 @@ var GoogleAdapter = class {
|
|
|
2286
2392
|
const input = [
|
|
2287
2393
|
{ type: "text", text: params.prompt }
|
|
2288
2394
|
];
|
|
2289
|
-
for (const
|
|
2290
|
-
const [meta, data] =
|
|
2395
|
+
for (const image2 of params.referenceImages ?? []) {
|
|
2396
|
+
const [meta, data] = image2.split(",");
|
|
2291
2397
|
input.push({
|
|
2292
2398
|
type: "image",
|
|
2293
2399
|
mime_type: meta?.match(/data:([^;]+)/)?.[1] ?? "image/png",
|
|
2294
|
-
data: data ??
|
|
2400
|
+
data: data ?? image2
|
|
2295
2401
|
});
|
|
2296
2402
|
}
|
|
2297
2403
|
const payload = await this.request(
|
|
@@ -2383,10 +2489,10 @@ var GoogleAdapter = class {
|
|
|
2383
2489
|
};
|
|
2384
2490
|
const first = params.imageUrl ?? params.referenceImages?.[0];
|
|
2385
2491
|
if (first) attach("image", first);
|
|
2386
|
-
for (const
|
|
2387
|
-
const
|
|
2388
|
-
if (
|
|
2389
|
-
const task =
|
|
2492
|
+
for (const image2 of params.imageUrls ?? []) attach("image", image2);
|
|
2493
|
+
const video2 = params.videoUrl ?? params.videoUrls?.[0];
|
|
2494
|
+
if (video2) attach("video", video2);
|
|
2495
|
+
const task = video2 ? "edit" : params.imageUrls?.length ? "reference_to_video" : first ? "image_to_video" : "text_to_video";
|
|
2390
2496
|
return {
|
|
2391
2497
|
model: modelId,
|
|
2392
2498
|
input,
|
|
@@ -2555,12 +2661,12 @@ var GoogleAdapter = class {
|
|
|
2555
2661
|
const input = [
|
|
2556
2662
|
{ type: "text", text: params.prompt }
|
|
2557
2663
|
];
|
|
2558
|
-
for (const
|
|
2559
|
-
const [meta, data] =
|
|
2664
|
+
for (const image2 of params.referenceImages ?? []) {
|
|
2665
|
+
const [meta, data] = image2.split(",");
|
|
2560
2666
|
input.push({
|
|
2561
2667
|
type: "image",
|
|
2562
2668
|
mime_type: meta?.match(/data:([^;]+)/)?.[1] ?? "image/png",
|
|
2563
|
-
data: data ??
|
|
2669
|
+
data: data ?? image2
|
|
2564
2670
|
});
|
|
2565
2671
|
}
|
|
2566
2672
|
return {
|
|
@@ -3370,11 +3476,11 @@ var QwenAdapter = class {
|
|
|
3370
3476
|
case "media": {
|
|
3371
3477
|
const media = [];
|
|
3372
3478
|
if (binding.mode === "video-edit") {
|
|
3373
|
-
const
|
|
3374
|
-
if (!
|
|
3479
|
+
const video2 = params.videoUrl ?? params.videoUrls?.[0];
|
|
3480
|
+
if (!video2) {
|
|
3375
3481
|
throw new Error(`Qwen "${modelId}" needs a video \u2014 pass videoUrl.`);
|
|
3376
3482
|
}
|
|
3377
|
-
media.push({ type: "video", url:
|
|
3483
|
+
media.push({ type: "video", url: video2 });
|
|
3378
3484
|
} else if (binding.mode === "r2v") {
|
|
3379
3485
|
for (const url of references) {
|
|
3380
3486
|
media.push({ type: "reference_image", url });
|
|
@@ -3414,7 +3520,7 @@ var QwenAdapter = class {
|
|
|
3414
3520
|
...output.video_url ? [output.video_url] : [],
|
|
3415
3521
|
...(output.results ?? []).map((result) => result.url).filter((url) => Boolean(url)),
|
|
3416
3522
|
...(output.choices ?? []).flatMap(
|
|
3417
|
-
(choice) => (choice.message?.content ?? []).map((part) => part.image).filter((
|
|
3523
|
+
(choice) => (choice.message?.content ?? []).map((part) => part.image).filter((image2) => Boolean(image2))
|
|
3418
3524
|
)
|
|
3419
3525
|
];
|
|
3420
3526
|
return urls.map((url) => ({
|
|
@@ -3562,8 +3668,8 @@ var QwenAdapter = class {
|
|
|
3562
3668
|
}
|
|
3563
3669
|
try {
|
|
3564
3670
|
const content = [];
|
|
3565
|
-
for (const
|
|
3566
|
-
content.push({ image });
|
|
3671
|
+
for (const image2 of params.referenceImages ?? []) {
|
|
3672
|
+
content.push({ image: image2 });
|
|
3567
3673
|
}
|
|
3568
3674
|
content.push({ text: params.prompt });
|
|
3569
3675
|
const parameters = {
|
|
@@ -3756,6 +3862,475 @@ var QwenAdapter = class {
|
|
|
3756
3862
|
}
|
|
3757
3863
|
};
|
|
3758
3864
|
|
|
3865
|
+
// src/adapters/topaz.adapter.ts
|
|
3866
|
+
var DEFAULT_BASE_URL7 = "https://api.topazlabs.com";
|
|
3867
|
+
var POLL_INTERVAL_MS6 = 5e3;
|
|
3868
|
+
var DEFAULT_MAX_POLL_ATTEMPTS6 = 240;
|
|
3869
|
+
var DEFAULT_FPS = 30;
|
|
3870
|
+
var PIXELS2 = {
|
|
3871
|
+
"720p": { width: 1280, height: 720 },
|
|
3872
|
+
"720P": { width: 1280, height: 720 },
|
|
3873
|
+
"1080p": { width: 1920, height: 1080 },
|
|
3874
|
+
"1080P": { width: 1920, height: 1080 },
|
|
3875
|
+
"4k": { width: 3840, height: 2160 },
|
|
3876
|
+
"4K": { width: 3840, height: 2160 },
|
|
3877
|
+
"1K": { width: 1024, height: 1024 },
|
|
3878
|
+
"2K": { width: 2048, height: 2048 }
|
|
3879
|
+
};
|
|
3880
|
+
var TopazAdapter = class {
|
|
3881
|
+
providerName = "topaz";
|
|
3882
|
+
supportedTypes = ["video", "image"];
|
|
3883
|
+
opts;
|
|
3884
|
+
constructor(opts) {
|
|
3885
|
+
this.opts = opts;
|
|
3886
|
+
}
|
|
3887
|
+
get baseUrl() {
|
|
3888
|
+
return (this.opts.baseUrl ?? DEFAULT_BASE_URL7).replace(/\/$/, "");
|
|
3889
|
+
}
|
|
3890
|
+
get headers() {
|
|
3891
|
+
return {
|
|
3892
|
+
"X-API-Key": this.opts.apiKey,
|
|
3893
|
+
accept: "application/json",
|
|
3894
|
+
"Content-Type": "application/json"
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
binding(modelId) {
|
|
3898
|
+
const id = modelId ?? "";
|
|
3899
|
+
const found = TOPAZ_MODELS[id];
|
|
3900
|
+
if (!found) {
|
|
3901
|
+
throw new Error(
|
|
3902
|
+
`"${id}" is not a Topaz model. Known: ${TOPAZ_CATALOG.map((model) => model.id).join(", ")}.`
|
|
3903
|
+
);
|
|
3904
|
+
}
|
|
3905
|
+
return [id, found];
|
|
3906
|
+
}
|
|
3907
|
+
/** 720p / 1080p / 4k, or an explicit "1920x1080" / "1920*1080" pair. */
|
|
3908
|
+
resolutionOf(resolution) {
|
|
3909
|
+
if (!resolution) return PIXELS2["1080p"];
|
|
3910
|
+
const named = PIXELS2[resolution];
|
|
3911
|
+
if (named) return named;
|
|
3912
|
+
const match = /^(\d{2,5})[x*](\d{2,5})$/i.exec(resolution.trim());
|
|
3913
|
+
if (match?.[1] && match[2]) {
|
|
3914
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
3915
|
+
}
|
|
3916
|
+
throw new Error(
|
|
3917
|
+
`Topaz wants 720p, 1080p, 4k or WxH, not "${resolution}".`
|
|
3918
|
+
);
|
|
3919
|
+
}
|
|
3920
|
+
containerOf(url) {
|
|
3921
|
+
const path = url.split("?")[0]?.toLowerCase() ?? "";
|
|
3922
|
+
if (path.endsWith(".mov")) return "mov";
|
|
3923
|
+
if (path.endsWith(".mkv")) return "mkv";
|
|
3924
|
+
return "mp4";
|
|
3925
|
+
}
|
|
3926
|
+
filterOf(binding, params) {
|
|
3927
|
+
const extras = params.providerOptions?.topaz ?? {};
|
|
3928
|
+
if (binding.kind === "upscale") {
|
|
3929
|
+
return { auto: "Auto", ...extras, model: binding.vendorModel };
|
|
3930
|
+
}
|
|
3931
|
+
const slowmo = numberish(extras.slowmo) ?? 1;
|
|
3932
|
+
const fps = numberish(extras.fps) ?? DEFAULT_FPS;
|
|
3933
|
+
if (slowmo < 1 || slowmo > 16) {
|
|
3934
|
+
throw new Error(`Topaz interpolation slowmo is 1\u201316, not ${slowmo}.`);
|
|
3935
|
+
}
|
|
3936
|
+
if (fps < 15 || fps > 240) {
|
|
3937
|
+
throw new Error(`Topaz interpolation fps is 15\u2013240, not ${fps}.`);
|
|
3938
|
+
}
|
|
3939
|
+
const filter = {
|
|
3940
|
+
model: binding.vendorModel,
|
|
3941
|
+
slowmo,
|
|
3942
|
+
fps
|
|
3943
|
+
};
|
|
3944
|
+
if (typeof extras.duplicate === "boolean") {
|
|
3945
|
+
filter.duplicate = extras.duplicate;
|
|
3946
|
+
}
|
|
3947
|
+
if (extras.duplicateThreshold !== void 0) {
|
|
3948
|
+
filter.duplicateThreshold = extras.duplicateThreshold;
|
|
3949
|
+
}
|
|
3950
|
+
return filter;
|
|
3951
|
+
}
|
|
3952
|
+
requestBody(binding, params, sourceUrl) {
|
|
3953
|
+
const extras = params.providerOptions?.topaz ?? {};
|
|
3954
|
+
const fps = numberish(extras.fps) ?? DEFAULT_FPS;
|
|
3955
|
+
return {
|
|
3956
|
+
source: { container: this.containerOf(sourceUrl) },
|
|
3957
|
+
filters: [this.filterOf(binding, params)],
|
|
3958
|
+
output: {
|
|
3959
|
+
resolution: this.resolutionOf(params.resolution),
|
|
3960
|
+
frameRate: fps,
|
|
3961
|
+
audioCodec: "AAC",
|
|
3962
|
+
audioTransfer: "Copy",
|
|
3963
|
+
dynamicCompressionLevel: "High",
|
|
3964
|
+
container: "mp4"
|
|
3965
|
+
}
|
|
3966
|
+
};
|
|
3967
|
+
}
|
|
3968
|
+
async request(path, init) {
|
|
3969
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
3970
|
+
method: init?.method ?? "GET",
|
|
3971
|
+
headers: this.headers,
|
|
3972
|
+
body: init?.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
3973
|
+
});
|
|
3974
|
+
const payload = await response.json();
|
|
3975
|
+
if (!response.ok) {
|
|
3976
|
+
throw new Error(
|
|
3977
|
+
payload.message ?? `Topaz returned ${response.status} for ${path}.`
|
|
3978
|
+
);
|
|
3979
|
+
}
|
|
3980
|
+
return payload;
|
|
3981
|
+
}
|
|
3982
|
+
async downloadSource(url) {
|
|
3983
|
+
const response = await fetch(url);
|
|
3984
|
+
if (!response.ok) {
|
|
3985
|
+
throw new Error(`Could not download the source video (${response.status}).`);
|
|
3986
|
+
}
|
|
3987
|
+
return {
|
|
3988
|
+
bytes: await response.arrayBuffer(),
|
|
3989
|
+
contentType: response.headers.get("content-type") ?? "video/mp4"
|
|
3990
|
+
};
|
|
3991
|
+
}
|
|
3992
|
+
async submitTask(params) {
|
|
3993
|
+
const [, binding] = this.binding(params.model);
|
|
3994
|
+
if (binding.surface !== "video") {
|
|
3995
|
+
throw new Error(
|
|
3996
|
+
`Topaz "${params.model}" is an image model. Use ai.image.`
|
|
3997
|
+
);
|
|
3998
|
+
}
|
|
3999
|
+
const sourceUrl = params.videoUrl ?? params.videoUrls?.[0];
|
|
4000
|
+
if (!sourceUrl) {
|
|
4001
|
+
throw new Error(
|
|
4002
|
+
`Topaz "${params.model}" needs a source video \u2014 pass videoUrl.`
|
|
4003
|
+
);
|
|
4004
|
+
}
|
|
4005
|
+
const source = await this.downloadSource(sourceUrl);
|
|
4006
|
+
const created = await this.request("/video/express", {
|
|
4007
|
+
method: "POST",
|
|
4008
|
+
body: this.requestBody(binding, params, sourceUrl)
|
|
4009
|
+
});
|
|
4010
|
+
const taskId = created.requestId?.trim();
|
|
4011
|
+
const uploadUrl = created.uploadUrls?.[0];
|
|
4012
|
+
if (!taskId || !uploadUrl) {
|
|
4013
|
+
throw new Error(
|
|
4014
|
+
"Topaz accepted the request but returned no request id or upload URL."
|
|
4015
|
+
);
|
|
4016
|
+
}
|
|
4017
|
+
const uploaded = await fetch(uploadUrl, {
|
|
4018
|
+
method: "PUT",
|
|
4019
|
+
headers: { "Content-Type": source.contentType },
|
|
4020
|
+
body: source.bytes
|
|
4021
|
+
});
|
|
4022
|
+
if (!uploaded.ok) {
|
|
4023
|
+
throw new Error(`Topaz upload failed (${uploaded.status}).`);
|
|
4024
|
+
}
|
|
4025
|
+
return { taskId, pollEndpoint: `/video/${taskId}/status` };
|
|
4026
|
+
}
|
|
4027
|
+
outputsFrom(payload, job) {
|
|
4028
|
+
const url = payload.download?.url;
|
|
4029
|
+
if (!url) return [];
|
|
4030
|
+
const expiresAtMs = payload.download?.expiresAt;
|
|
4031
|
+
return [
|
|
4032
|
+
{
|
|
4033
|
+
url,
|
|
4034
|
+
mimeType: "video/mp4",
|
|
4035
|
+
taskId: job.id,
|
|
4036
|
+
expiresAt: expiresAtMs ? new Date(expiresAtMs).toISOString() : expiresInHours(24),
|
|
4037
|
+
raw: {
|
|
4038
|
+
status: payload.status,
|
|
4039
|
+
progress: payload.progress,
|
|
4040
|
+
estimates: payload.estimates,
|
|
4041
|
+
outputSize: payload.outputSize
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
];
|
|
4045
|
+
}
|
|
4046
|
+
snapshot(payload, job) {
|
|
4047
|
+
const raw = (payload.status ?? "").toLowerCase();
|
|
4048
|
+
if (raw === "failed" || raw === "canceled") {
|
|
4049
|
+
return {
|
|
4050
|
+
status: "failed",
|
|
4051
|
+
error: payload.message ?? `Topaz request ${raw}.`
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
if (raw !== "complete") return { status: "pending" };
|
|
4055
|
+
const outputs = this.outputsFrom(payload, job);
|
|
4056
|
+
if (outputs.length === 0) return { status: "pending" };
|
|
4057
|
+
return {
|
|
4058
|
+
status: "succeeded",
|
|
4059
|
+
result: {
|
|
4060
|
+
success: true,
|
|
4061
|
+
outputs,
|
|
4062
|
+
creditsUsed: 0,
|
|
4063
|
+
provider: this.providerName,
|
|
4064
|
+
model: job.model,
|
|
4065
|
+
processingTimeMs: 0
|
|
4066
|
+
}
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
4069
|
+
async run(params) {
|
|
4070
|
+
const startedAt = Date.now();
|
|
4071
|
+
let modelId = params.model ?? "(none)";
|
|
4072
|
+
const failure = (error) => ({
|
|
4073
|
+
success: false,
|
|
4074
|
+
outputs: [],
|
|
4075
|
+
creditsUsed: 0,
|
|
4076
|
+
provider: this.providerName,
|
|
4077
|
+
model: modelId,
|
|
4078
|
+
processingTimeMs: Date.now() - startedAt,
|
|
4079
|
+
error
|
|
4080
|
+
});
|
|
4081
|
+
let submitted;
|
|
4082
|
+
try {
|
|
4083
|
+
modelId = this.binding(params.model)[0];
|
|
4084
|
+
submitted = await this.submitTask(params);
|
|
4085
|
+
} catch (error) {
|
|
4086
|
+
if (isPendingJob(error)) throw error;
|
|
4087
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
4088
|
+
}
|
|
4089
|
+
if (isSubmitMode()) {
|
|
4090
|
+
throw new PendingJob(submitted.taskId, submitted.pollEndpoint);
|
|
4091
|
+
}
|
|
4092
|
+
const job = {
|
|
4093
|
+
id: submitted.taskId,
|
|
4094
|
+
provider: this.providerName,
|
|
4095
|
+
model: modelId,
|
|
4096
|
+
kind: "video",
|
|
4097
|
+
pollEndpoint: submitted.pollEndpoint,
|
|
4098
|
+
params,
|
|
4099
|
+
submittedAt: new Date(startedAt).toISOString()
|
|
4100
|
+
};
|
|
4101
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
|
|
4102
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
4103
|
+
const snapshot = await this.completeJob(job);
|
|
4104
|
+
if (snapshot.status === "failed") {
|
|
4105
|
+
return failure(snapshot.error ?? "Topaz request failed.");
|
|
4106
|
+
}
|
|
4107
|
+
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
4108
|
+
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
4109
|
+
}
|
|
4110
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
|
|
4111
|
+
}
|
|
4112
|
+
return failure(
|
|
4113
|
+
`Topaz request ${submitted.taskId} did not finish after ${maxAttempts} checks.`
|
|
4114
|
+
);
|
|
4115
|
+
}
|
|
4116
|
+
async completeJob(job) {
|
|
4117
|
+
try {
|
|
4118
|
+
if (job.pollEndpoint?.startsWith("/image/")) {
|
|
4119
|
+
return this.imageSnapshot(job);
|
|
4120
|
+
}
|
|
4121
|
+
const payload = await this.request(
|
|
4122
|
+
job.pollEndpoint ?? `/video/${job.id}/status`
|
|
4123
|
+
);
|
|
4124
|
+
return this.snapshot(payload, job);
|
|
4125
|
+
} catch (error) {
|
|
4126
|
+
return {
|
|
4127
|
+
status: "failed",
|
|
4128
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4129
|
+
};
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
generateVideo(params) {
|
|
4133
|
+
return this.run(params);
|
|
4134
|
+
}
|
|
4135
|
+
imageBody(binding, params, sourceUrl) {
|
|
4136
|
+
const extras = params.providerOptions?.topaz ?? {};
|
|
4137
|
+
const pixels = params.resolution ? this.resolutionOf(params.resolution) : void 0;
|
|
4138
|
+
const fields = {
|
|
4139
|
+
source_url: sourceUrl,
|
|
4140
|
+
model: binding.vendorModel,
|
|
4141
|
+
output_format: params.outputFormat === "png" ? "png" : params.outputFormat === "jpeg" ? "jpeg" : "jpeg"
|
|
4142
|
+
};
|
|
4143
|
+
if (pixels) {
|
|
4144
|
+
fields.output_width = String(pixels.width);
|
|
4145
|
+
fields.output_height = String(pixels.height);
|
|
4146
|
+
}
|
|
4147
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
4148
|
+
if (value === void 0) continue;
|
|
4149
|
+
fields[key] = typeof value === "string" ? value : String(value);
|
|
4150
|
+
}
|
|
4151
|
+
return fields;
|
|
4152
|
+
}
|
|
4153
|
+
async submitImage(params) {
|
|
4154
|
+
const [, binding] = this.binding(params.model);
|
|
4155
|
+
if (binding.surface !== "image" || !binding.imagePath) {
|
|
4156
|
+
throw new Error(
|
|
4157
|
+
`Topaz "${params.model}" is a video model. Use ai.video with videoUrl.`
|
|
4158
|
+
);
|
|
4159
|
+
}
|
|
4160
|
+
const sourceUrl = params.referenceImages?.[0];
|
|
4161
|
+
if (!sourceUrl) {
|
|
4162
|
+
throw new Error(
|
|
4163
|
+
`Topaz "${params.model}" needs a source image \u2014 pass referenceImages.`
|
|
4164
|
+
);
|
|
4165
|
+
}
|
|
4166
|
+
const form = new FormData();
|
|
4167
|
+
for (const [key, value] of Object.entries(
|
|
4168
|
+
this.imageBody(binding, params, sourceUrl)
|
|
4169
|
+
)) {
|
|
4170
|
+
form.set(key, value);
|
|
4171
|
+
}
|
|
4172
|
+
const response = await fetch(`${this.baseUrl}${binding.imagePath}`, {
|
|
4173
|
+
method: "POST",
|
|
4174
|
+
headers: {
|
|
4175
|
+
"X-API-Key": this.opts.apiKey,
|
|
4176
|
+
accept: "application/json"
|
|
4177
|
+
},
|
|
4178
|
+
body: form
|
|
4179
|
+
});
|
|
4180
|
+
const payload = await response.json();
|
|
4181
|
+
if (!response.ok) {
|
|
4182
|
+
throw new Error(
|
|
4183
|
+
payload.message ?? `Topaz returned ${response.status} enhancing the image.`
|
|
4184
|
+
);
|
|
4185
|
+
}
|
|
4186
|
+
const taskId = payload.process_id?.trim();
|
|
4187
|
+
if (!taskId) {
|
|
4188
|
+
throw new Error(
|
|
4189
|
+
"Topaz accepted the image but returned no process id."
|
|
4190
|
+
);
|
|
4191
|
+
}
|
|
4192
|
+
return { taskId, pollEndpoint: `/image/v1/status/${taskId}` };
|
|
4193
|
+
}
|
|
4194
|
+
async imageSnapshot(job) {
|
|
4195
|
+
const payload = await this.request(
|
|
4196
|
+
job.pollEndpoint ?? `/image/v1/status/${job.id}`
|
|
4197
|
+
);
|
|
4198
|
+
const raw = (payload.status ?? "").toLowerCase();
|
|
4199
|
+
if (raw === "failed" || raw === "cancelled") {
|
|
4200
|
+
return {
|
|
4201
|
+
status: "failed",
|
|
4202
|
+
error: `Topaz image ${raw}.`
|
|
4203
|
+
};
|
|
4204
|
+
}
|
|
4205
|
+
if (raw !== "completed") return { status: "pending" };
|
|
4206
|
+
let url = payload.download_url;
|
|
4207
|
+
let expiresAt = payload.eta ? new Date(payload.eta > 1e10 ? payload.eta : payload.eta * 1e3).toISOString() : void 0;
|
|
4208
|
+
if (!url) {
|
|
4209
|
+
const download = await this.request(
|
|
4210
|
+
`/image/v1/download/${job.id}`
|
|
4211
|
+
);
|
|
4212
|
+
url = download.download_url;
|
|
4213
|
+
if (download.expiry) {
|
|
4214
|
+
expiresAt = new Date(
|
|
4215
|
+
download.expiry > 1e10 ? download.expiry : download.expiry * 1e3
|
|
4216
|
+
).toISOString();
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
if (!url) return { status: "pending" };
|
|
4220
|
+
return {
|
|
4221
|
+
status: "succeeded",
|
|
4222
|
+
result: {
|
|
4223
|
+
success: true,
|
|
4224
|
+
outputs: [
|
|
4225
|
+
{
|
|
4226
|
+
url,
|
|
4227
|
+
mimeType: "image/jpeg",
|
|
4228
|
+
taskId: job.id,
|
|
4229
|
+
expiresAt: expiresAt ?? expiresInHours(1),
|
|
4230
|
+
raw: {
|
|
4231
|
+
status: payload.status,
|
|
4232
|
+
progress: payload.progress,
|
|
4233
|
+
model: payload.model
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
],
|
|
4237
|
+
creditsUsed: payload.credits ?? 0,
|
|
4238
|
+
provider: this.providerName,
|
|
4239
|
+
model: job.model,
|
|
4240
|
+
processingTimeMs: 0
|
|
4241
|
+
}
|
|
4242
|
+
};
|
|
4243
|
+
}
|
|
4244
|
+
async runImage(params) {
|
|
4245
|
+
const startedAt = Date.now();
|
|
4246
|
+
let modelId = params.model ?? "(none)";
|
|
4247
|
+
const failure = (error) => ({
|
|
4248
|
+
success: false,
|
|
4249
|
+
outputs: [],
|
|
4250
|
+
creditsUsed: 0,
|
|
4251
|
+
provider: this.providerName,
|
|
4252
|
+
model: modelId,
|
|
4253
|
+
processingTimeMs: Date.now() - startedAt,
|
|
4254
|
+
error
|
|
4255
|
+
});
|
|
4256
|
+
let submitted;
|
|
4257
|
+
try {
|
|
4258
|
+
modelId = this.binding(params.model)[0];
|
|
4259
|
+
submitted = await this.submitImage(params);
|
|
4260
|
+
} catch (error) {
|
|
4261
|
+
if (isPendingJob(error)) throw error;
|
|
4262
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
4263
|
+
}
|
|
4264
|
+
if (isSubmitMode()) {
|
|
4265
|
+
throw new PendingJob(submitted.taskId, submitted.pollEndpoint);
|
|
4266
|
+
}
|
|
4267
|
+
const job = {
|
|
4268
|
+
id: submitted.taskId,
|
|
4269
|
+
provider: this.providerName,
|
|
4270
|
+
model: modelId,
|
|
4271
|
+
kind: "image",
|
|
4272
|
+
pollEndpoint: submitted.pollEndpoint,
|
|
4273
|
+
params,
|
|
4274
|
+
submittedAt: new Date(startedAt).toISOString()
|
|
4275
|
+
};
|
|
4276
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
|
|
4277
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
4278
|
+
const snapshot = await this.completeJob(job);
|
|
4279
|
+
if (snapshot.status === "failed") {
|
|
4280
|
+
return failure(snapshot.error ?? "Topaz image failed.");
|
|
4281
|
+
}
|
|
4282
|
+
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
4283
|
+
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
4284
|
+
}
|
|
4285
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
|
|
4286
|
+
}
|
|
4287
|
+
return failure(
|
|
4288
|
+
`Topaz image ${submitted.taskId} did not finish after ${maxAttempts} checks.`
|
|
4289
|
+
);
|
|
4290
|
+
}
|
|
4291
|
+
generateImage(params) {
|
|
4292
|
+
return this.runImage(params);
|
|
4293
|
+
}
|
|
4294
|
+
generateText(params) {
|
|
4295
|
+
return Promise.resolve(unsupported(this.providerName, params.model, "text"));
|
|
4296
|
+
}
|
|
4297
|
+
generateAudio(params) {
|
|
4298
|
+
return Promise.resolve(unsupported(this.providerName, params.model, "audio"));
|
|
4299
|
+
}
|
|
4300
|
+
estimateCost(type, params) {
|
|
4301
|
+
return Promise.resolve(estimateFor(this.providerName, type, params));
|
|
4302
|
+
}
|
|
4303
|
+
supportsModel(model) {
|
|
4304
|
+
return model in TOPAZ_MODELS;
|
|
4305
|
+
}
|
|
4306
|
+
getAvailableModels() {
|
|
4307
|
+
return TOPAZ_CATALOG.map((model) => ({
|
|
4308
|
+
id: model.id,
|
|
4309
|
+
name: model.name,
|
|
4310
|
+
type: model.category
|
|
4311
|
+
}));
|
|
4312
|
+
}
|
|
4313
|
+
};
|
|
4314
|
+
function numberish(value) {
|
|
4315
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4316
|
+
if (typeof value === "string" && value.length > 0) {
|
|
4317
|
+
const parsed = Number(value);
|
|
4318
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
4319
|
+
}
|
|
4320
|
+
return void 0;
|
|
4321
|
+
}
|
|
4322
|
+
function unsupported(provider, model, kind) {
|
|
4323
|
+
return {
|
|
4324
|
+
success: false,
|
|
4325
|
+
outputs: [],
|
|
4326
|
+
creditsUsed: 0,
|
|
4327
|
+
provider,
|
|
4328
|
+
model: model ?? "",
|
|
4329
|
+
processingTimeMs: 0,
|
|
4330
|
+
error: `Topaz does not generate ${kind} on this adapter.`
|
|
4331
|
+
};
|
|
4332
|
+
}
|
|
4333
|
+
|
|
3759
4334
|
// src/helpers/result.ts
|
|
3760
4335
|
function ok(data) {
|
|
3761
4336
|
return { data, error: null };
|
|
@@ -3956,7 +4531,8 @@ var NATIVE_PROVIDERS = [
|
|
|
3956
4531
|
"google",
|
|
3957
4532
|
"kling",
|
|
3958
4533
|
"openai",
|
|
3959
|
-
"qwen"
|
|
4534
|
+
"qwen",
|
|
4535
|
+
"topaz"
|
|
3960
4536
|
];
|
|
3961
4537
|
function brotu(options) {
|
|
3962
4538
|
const apiKey = options.apiKey?.trim();
|
|
@@ -4055,6 +4631,12 @@ function brotu(options) {
|
|
|
4055
4631
|
baseUrl: provider.baseUrl
|
|
4056
4632
|
});
|
|
4057
4633
|
}
|
|
4634
|
+
if (provider.id === "topaz") {
|
|
4635
|
+
return new TopazAdapter({
|
|
4636
|
+
apiKey: provider.apiKey,
|
|
4637
|
+
baseUrl: provider.baseUrl
|
|
4638
|
+
});
|
|
4639
|
+
}
|
|
4058
4640
|
return void 0;
|
|
4059
4641
|
}
|
|
4060
4642
|
function generateWith(adapter, kind, params) {
|
|
@@ -4373,12 +4955,67 @@ function brotu(options) {
|
|
|
4373
4955
|
image: {
|
|
4374
4956
|
submit: (params) => submit("image", params),
|
|
4375
4957
|
generate: (params) => generate("image", params),
|
|
4376
|
-
list: () => listFor("image")
|
|
4958
|
+
list: () => listFor("image"),
|
|
4959
|
+
upscale: (params) => {
|
|
4960
|
+
if (!params.imageUrl?.trim()) {
|
|
4961
|
+
return Promise.resolve(
|
|
4962
|
+
fail({
|
|
4963
|
+
code: "invalid_request",
|
|
4964
|
+
message: "image.upscale needs imageUrl.",
|
|
4965
|
+
model: params.model
|
|
4966
|
+
})
|
|
4967
|
+
);
|
|
4968
|
+
}
|
|
4969
|
+
const model = getModel(params.model);
|
|
4970
|
+
if (model && !model.nodeTypes.includes("image_upscale")) {
|
|
4971
|
+
return Promise.resolve(
|
|
4972
|
+
fail({
|
|
4973
|
+
code: "invalid_request",
|
|
4974
|
+
message: `"${params.model}" is not an image upscale model.`,
|
|
4975
|
+
model: params.model
|
|
4976
|
+
})
|
|
4977
|
+
);
|
|
4978
|
+
}
|
|
4979
|
+
return generate("image", {
|
|
4980
|
+
...params,
|
|
4981
|
+
prompt: params.prompt ?? "",
|
|
4982
|
+
referenceImages: [
|
|
4983
|
+
params.imageUrl,
|
|
4984
|
+
...params.referenceImages ?? []
|
|
4985
|
+
]
|
|
4986
|
+
});
|
|
4987
|
+
}
|
|
4377
4988
|
},
|
|
4378
4989
|
video: {
|
|
4379
4990
|
submit: (params) => submit("video", params),
|
|
4380
4991
|
generate: (params) => generate("video", params),
|
|
4381
|
-
list: () => listFor("video")
|
|
4992
|
+
list: () => listFor("video"),
|
|
4993
|
+
upscale: (params) => {
|
|
4994
|
+
if (!params.videoUrl?.trim()) {
|
|
4995
|
+
return Promise.resolve(
|
|
4996
|
+
fail({
|
|
4997
|
+
code: "invalid_request",
|
|
4998
|
+
message: "video.upscale needs videoUrl.",
|
|
4999
|
+
model: params.model
|
|
5000
|
+
})
|
|
5001
|
+
);
|
|
5002
|
+
}
|
|
5003
|
+
const model = getModel(params.model);
|
|
5004
|
+
if (model && !model.nodeTypes.includes("video_upscale")) {
|
|
5005
|
+
return Promise.resolve(
|
|
5006
|
+
fail({
|
|
5007
|
+
code: "invalid_request",
|
|
5008
|
+
message: `"${params.model}" is not a video upscale model. Interpolation models use ai.video.submit.`,
|
|
5009
|
+
model: params.model
|
|
5010
|
+
})
|
|
5011
|
+
);
|
|
5012
|
+
}
|
|
5013
|
+
return submit("video", {
|
|
5014
|
+
...params,
|
|
5015
|
+
prompt: params.prompt ?? "",
|
|
5016
|
+
videoUrl: params.videoUrl
|
|
5017
|
+
});
|
|
5018
|
+
}
|
|
4382
5019
|
},
|
|
4383
5020
|
text: {
|
|
4384
5021
|
submit: (params) => submit("text", params),
|
|
@@ -4487,6 +5124,11 @@ function brotu(options) {
|
|
|
4487
5124
|
QWEN_TEXT_MODELS,
|
|
4488
5125
|
QWEN_VIDEO_MODELS,
|
|
4489
5126
|
QwenAdapter,
|
|
5127
|
+
TOPAZ_CATALOG,
|
|
5128
|
+
TOPAZ_IMAGE_MODELS,
|
|
5129
|
+
TOPAZ_MODELS,
|
|
5130
|
+
TOPAZ_VIDEO_MODELS,
|
|
5131
|
+
TopazAdapter,
|
|
4490
5132
|
brotu,
|
|
4491
5133
|
createS3Storage,
|
|
4492
5134
|
deliverWebhook,
|