@shop-os/modelkit 0.2.27 → 0.3.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/CHANGELOG.md +19 -0
- package/README.md +20 -1
- package/dist/catalog.cjs +288 -14
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +158 -2
- package/dist/catalog.d.ts +158 -2
- package/dist/catalog.js +288 -14
- package/dist/catalog.js.map +1 -1
- package/dist/index.cjs +675 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +194 -2
- package/dist/index.d.ts +194 -2
- package/dist/index.js +670 -137
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,25 @@ header in the release PR.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.3.0] - Unreleased
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Row type `video-tool`: video in, video out, no prompt. `constraints.tool` says what the row does (`kind: "upscale" | "interpolate" | "hdr"`) and its limits (`upscaleFactor`, `maxOutputLongEdge`, `fps`, `outputFormats`). Rows `topaz-precision` (Proteus) and `topaz-starlight` (Starlight Precise 2.6) upscale up to 4x with a 3840 px long edge, `topaz-apollo` interpolates to 16-120 fps, and `topaz-hyperion-2.5` writes an HDR master in H.265 (HDR10) or ProRes. All four run on fal, cap the source at 300 s, and are listed on `cowork` only.
|
|
16
|
+
- `kit.submitVideoTool(req, { webhookUrl })` submits to fal's queue (with `fal_webhook`) and returns a JSON `VideoToolSubmission`; `videoToolStatus`, `videoToolResult` (from a verified webhook body or the stored result) and `cancelVideoTool` take it back from any process. Measured on 2026-10-02: a 30 s 1080p-to-4K Starlight job took 2,593 s and a 30 s 4K SDR to HDR job 6,695 s, so callers must not wait inside one task. Submits are one attempt: a retry could start a second paid job. Stored URLs outside `https://queue.fal.run` are refused before the key is sent.
|
|
17
|
+
- `planVideoTool(row, req)` (also on `@shop-os/modelkit/catalog`): the output size, fps, resolution tier and format a request produces, or why the row refuses it. `resolutionTier(width, height)` tiers by the short edge. `UsageEvent.fps` carries the output frame rate.
|
|
18
|
+
- `falWebhookBodySchema` parses fal's webhook body.
|
|
19
|
+
- `capabilities.videoEdit` marks the rows offered for prompt-driven edits: `true` on `seedance-2.5`, `gemini-omni-flash`, `gemini-omni-1.1-flash`; `false` on `seedance-2.0` and `seedance-2.0-fast`, which take a source video but are not offered for edits. A row without the flag is read as `acceptsSourceVideo` by Studio.
|
|
20
|
+
- `constraints.references.audioSeconds` (`{ min?, max?, total? }`), `audioFormats` (lowercase extensions) and `audioMaxBytes` describe reference audio. Set from BytePlus's docs on `seedance-2.5` (2-30 s per clip, 30 s in all) and `seedance-2.0` / `seedance-2.0-fast` (2-15 s per clip, 15 s in all), all `wav`/`mp3` up to 15 MB.
|
|
21
|
+
- `constraints.extension.returns`: `"joined"` (absent, Omni) returns the whole clip so far; `"new"` returns only the added footage. `seedance-2.5` gains `videoExtension` and `extension: { segmentSeconds: 30, maxTotalSeconds: 60, returns: "new" }`; a live extend on 2026-10-02 returned only the 4 s it added, at the requested resolution.
|
|
22
|
+
|
|
23
|
+
### Live rows to add or edit after publish
|
|
24
|
+
Older kits drop a `video-tool` row (unknown type) with a warning and keep loading the catalog. Pro-backend's admin API validates rows with this kit, so it must run 0.3.0 before the rows can be saved. In each environment, once pro-backend and cowork run 0.3.0 and the prices exist:
|
|
25
|
+
- Add `topaz-precision`, `topaz-starlight`, `topaz-apollo` and `topaz-hyperion-2.5` as bundled.
|
|
26
|
+
- `seedance-2.5`: add `videoExtension` to `constraints.supports`, set `constraints.extension` to `{ "segmentSeconds": 30, "maxTotalSeconds": 60, "returns": "new" }` and `capabilities.videoEdit` to `true`.
|
|
27
|
+
- `gemini-omni-flash`, `gemini-omni-1.1-flash`: set `capabilities.videoEdit` to `true`; on `gemini-omni-1.1-flash` add `"returns": "joined"` to `constraints.extension`.
|
|
28
|
+
- `seedance-2.0`, `seedance-2.0-fast`: set `capabilities.videoEdit` to `false`, and add `"audioSeconds": { "min": 2, "max": 15, "total": 15 }, "audioFormats": ["wav", "mp3"], "audioMaxBytes": 15728640` to `constraints.references`.
|
|
29
|
+
- `seedance-2.5`: add `"audioSeconds": { "min": 2, "max": 30, "total": 30 }, "audioFormats": ["wav", "mp3"], "audioMaxBytes": 15728640` to `constraints.references`.
|
|
30
|
+
|
|
12
31
|
## [0.2.27] - Unreleased
|
|
13
32
|
|
|
14
33
|
### Fixed
|
package/README.md
CHANGED
|
@@ -51,6 +51,25 @@ await kit.generateText({
|
|
|
51
51
|
await clip.cleanup?.();
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
### Video tools
|
|
55
|
+
|
|
56
|
+
`video-tool` rows (Topaz upscale, frame interpolation, SDR to HDR on fal) take a clip and
|
|
57
|
+
return a clip with no prompt. They run as provider jobs the caller tracks, because a 30 s
|
|
58
|
+
4K job can take over 40 minutes:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const plan = planVideoTool(kit.describe("topaz-precision"), { source, upscaleFactor: 2 }); // validate + price
|
|
62
|
+
const sub = await kit.submitVideoTool(
|
|
63
|
+
{ model: "topaz-precision", source, upscaleFactor: 2 },
|
|
64
|
+
{ webhookUrl }, // fal posts here when the job ends
|
|
65
|
+
);
|
|
66
|
+
// store `sub` (plain JSON); later, in any process:
|
|
67
|
+
const out = await kit.videoToolResult(sub, { webhook: verifiedBody }); // or poll videoToolStatus(sub)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`source` carries the caller's measured `width`, `height`, `durationSeconds` and `fps`; the kit
|
|
71
|
+
never measures. Verifying the webhook signature is the caller's job.
|
|
72
|
+
|
|
54
73
|
Keys come from `process.env` by default: bare names for pro (`OPENAI_API_KEY`, `FAL_KEY`, `ARK_API_KEY` ...), the `ENTERPRISE_` prefix for enterprise and the `SLOOSH_` prefix for sloosh. Pass `keys: { pro, enterprise, sloosh }` to override. `tier: "sloosh"` uses the sloosh keys only: a missing key is a `ModelKitError` with code `auth`, never a fallback to pro.
|
|
55
74
|
|
|
56
75
|
## Browser-safe catalog
|
|
@@ -252,7 +271,7 @@ const defaultKit = createDefaultModelKit({ keys: { pro: keysFromEnv(process.env)
|
|
|
252
271
|
|
|
253
272
|
## Add a model
|
|
254
273
|
|
|
255
|
-
1. Add a row to `src/catalog/{image,video,text,other}.ts`. Copy `constraints` from the provider docs.
|
|
274
|
+
1. Add a row to `src/catalog/{image,video,video-tools,text,other}.ts`. Copy `constraints` from the provider docs.
|
|
256
275
|
2. Run `pnpm test`. The catalog test checks id and alias uniqueness.
|
|
257
276
|
3. Bump `version` in `package.json` in a PR that touches only `packages/modelkit/**`.
|
|
258
277
|
Merging it to `dev` runs `modelkit-publish.yml`, which publishes through the npm
|
package/dist/catalog.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(catalog_entry_exports, {
|
|
|
36
36
|
SurfaceSchema: () => SurfaceSchema,
|
|
37
37
|
TEXT_MODELS: () => TEXT_MODELS,
|
|
38
38
|
VIDEO_MODELS: () => VIDEO_MODELS,
|
|
39
|
+
VIDEO_TOOL_MODELS: () => VIDEO_TOOL_MODELS,
|
|
39
40
|
billedAudio: () => billedAudio,
|
|
40
41
|
compareCatalogOrder: () => compareCatalogOrder,
|
|
41
42
|
isListed: () => isListed,
|
|
@@ -46,15 +47,18 @@ __export(catalog_entry_exports, {
|
|
|
46
47
|
nearestAspectRatio: () => nearestAspectRatio,
|
|
47
48
|
normalizeExposure: () => normalizeExposure,
|
|
48
49
|
outputAudio: () => outputAudio,
|
|
50
|
+
planVideoTool: () => planVideoTool,
|
|
49
51
|
primeCatalog: () => primeCatalog,
|
|
50
52
|
promptConventions: () => promptConventions,
|
|
51
53
|
resetLiveCatalog: () => resetLiveCatalog,
|
|
54
|
+
resolutionTier: () => resolutionTier,
|
|
52
55
|
resolveAudioOptions: () => resolveAudioOptions,
|
|
53
56
|
resolveImageOptions: () => resolveImageOptions,
|
|
54
57
|
resolveOptions: () => resolveOptions,
|
|
55
58
|
resolveVideoOptions: () => resolveVideoOptions,
|
|
56
59
|
sortCatalogRows: () => sortCatalogRows,
|
|
57
|
-
videoLengthViolation: () => videoLengthViolation
|
|
60
|
+
videoLengthViolation: () => videoLengthViolation,
|
|
61
|
+
videoToolConstraints: () => videoToolConstraints
|
|
58
62
|
});
|
|
59
63
|
module.exports = __toCommonJS(catalog_entry_exports);
|
|
60
64
|
|
|
@@ -1353,6 +1357,11 @@ var NEGATIVE_PROMPT = "blur, distort, and low quality";
|
|
|
1353
1357
|
var FRAMES_EXCLUDE_REFERENCES = { framesExcludeReferences: true };
|
|
1354
1358
|
var KLING_PROMPT = { referenceSyntax: "numbered", maxChars: 2500 };
|
|
1355
1359
|
var SEEDANCE_PROMPT = { referenceSyntax: "numbered", maxChars: 3e4 };
|
|
1360
|
+
var SEEDANCE_2_AUDIO = {
|
|
1361
|
+
audioSeconds: { min: 2, max: 15, total: 15 },
|
|
1362
|
+
audioFormats: ["wav", "mp3"],
|
|
1363
|
+
audioMaxBytes: 15 * 1024 * 1024
|
|
1364
|
+
};
|
|
1356
1365
|
var H3_PROMPT = { referenceSyntax: "numbered", maxChars: 5e4 };
|
|
1357
1366
|
var OMNI_PROMPT = { referenceSyntax: "caption", maxChars: 5e4 };
|
|
1358
1367
|
var BYTEDANCE_LOGO = "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/3bba5d81-4e57-4e71-911b-cdfa05acca2c_compressed.png";
|
|
@@ -1489,7 +1498,7 @@ var VIDEO_MODELS = [
|
|
|
1489
1498
|
description: "Google's conversational video model with native audio, human support, and natural language editing. Aspect ratio only \u2014 resolution and duration are model-determined.",
|
|
1490
1499
|
logo: GOOGLE_LOGO2
|
|
1491
1500
|
},
|
|
1492
|
-
capabilities: { acceptsSourceVideo: true }
|
|
1501
|
+
capabilities: { acceptsSourceVideo: true, videoEdit: true }
|
|
1493
1502
|
},
|
|
1494
1503
|
{
|
|
1495
1504
|
id: "gemini-omni-1.1-flash",
|
|
@@ -1514,7 +1523,7 @@ var VIDEO_MODELS = [
|
|
|
1514
1523
|
// with a video task, so those calls carry the prompt alone).
|
|
1515
1524
|
// First-to-last-frame interpolation: `<LAST_FRAME>` goes with `<FIRST_FRAME>`.
|
|
1516
1525
|
supports: ["videoExtension", "lastFrame"],
|
|
1517
|
-
extension: { segmentSeconds: 10, maxTotalSeconds: 40 },
|
|
1526
|
+
extension: { segmentSeconds: 10, maxTotalSeconds: 40, returns: "joined" },
|
|
1518
1527
|
prompt: OMNI_PROMPT,
|
|
1519
1528
|
audio: "always"
|
|
1520
1529
|
},
|
|
@@ -1526,7 +1535,11 @@ var VIDEO_MODELS = [
|
|
|
1526
1535
|
logo: GOOGLE_LOGO2,
|
|
1527
1536
|
description: "Google Gemini Omni Flash 1.1 conversational video generation, editing and extension; text, image, and up to 10-second video input, 3-10 second output at up to 4K and 24 FPS, extendable to 40 seconds."
|
|
1528
1537
|
},
|
|
1529
|
-
capabilities: {
|
|
1538
|
+
capabilities: {
|
|
1539
|
+
acceptsSourceVideo: true,
|
|
1540
|
+
videoEdit: true,
|
|
1541
|
+
lastFrameRequiresFirstFrame: true
|
|
1542
|
+
}
|
|
1530
1543
|
},
|
|
1531
1544
|
{
|
|
1532
1545
|
// vibe-triggers and Space Builder drop the ratio when a frame is wired; Cowork always sends it.
|
|
@@ -1548,7 +1561,8 @@ var VIDEO_MODELS = [
|
|
|
1548
1561
|
videos: 3,
|
|
1549
1562
|
audios: 3,
|
|
1550
1563
|
audioOnly: false,
|
|
1551
|
-
videoSeconds: { min: 2, max: 15, total: 15 }
|
|
1564
|
+
videoSeconds: { min: 2, max: 15, total: 15 },
|
|
1565
|
+
...SEEDANCE_2_AUDIO
|
|
1552
1566
|
},
|
|
1553
1567
|
sourceVideoSeconds: { min: 2, max: 15, inReferenceTotal: true },
|
|
1554
1568
|
ratioIgnoredWith: ["firstFrame", "sourceVideo"],
|
|
@@ -1570,7 +1584,7 @@ var VIDEO_MODELS = [
|
|
|
1570
1584
|
description: "High-quality AI video generation with reference image conditioning. Supports 4-15 second clips with human preprocessing.",
|
|
1571
1585
|
logo: BYTEDANCE_LOGO
|
|
1572
1586
|
},
|
|
1573
|
-
capabilities: { acceptsSourceVideo: true, ...FRAMES_EXCLUDE_REFERENCES }
|
|
1587
|
+
capabilities: { acceptsSourceVideo: true, videoEdit: false, ...FRAMES_EXCLUDE_REFERENCES }
|
|
1574
1588
|
},
|
|
1575
1589
|
{
|
|
1576
1590
|
id: "seedance-2.0-fast",
|
|
@@ -1590,7 +1604,8 @@ var VIDEO_MODELS = [
|
|
|
1590
1604
|
videos: 3,
|
|
1591
1605
|
audios: 3,
|
|
1592
1606
|
audioOnly: false,
|
|
1593
|
-
videoSeconds: { min: 2, max: 15, total: 15 }
|
|
1607
|
+
videoSeconds: { min: 2, max: 15, total: 15 },
|
|
1608
|
+
...SEEDANCE_2_AUDIO
|
|
1594
1609
|
},
|
|
1595
1610
|
sourceVideoSeconds: { min: 2, max: 15, inReferenceTotal: true },
|
|
1596
1611
|
ratioIgnoredWith: ["firstFrame", "sourceVideo"],
|
|
@@ -1612,7 +1627,7 @@ var VIDEO_MODELS = [
|
|
|
1612
1627
|
logo: BYTEDANCE_LOGO,
|
|
1613
1628
|
description: "Speed- and cost-optimized ByteDance Seedance 2.0 Fast; text plus image, video, and audio references to generate or edit 4-15 second clips."
|
|
1614
1629
|
},
|
|
1615
|
-
capabilities: { acceptsSourceVideo: true, ...FRAMES_EXCLUDE_REFERENCES }
|
|
1630
|
+
capabilities: { acceptsSourceVideo: true, videoEdit: false, ...FRAMES_EXCLUDE_REFERENCES }
|
|
1616
1631
|
},
|
|
1617
1632
|
{
|
|
1618
1633
|
// omni_reference_task_type edit/extend: adaptive ratio, edit sends duration -1.
|
|
@@ -1632,12 +1647,20 @@ var VIDEO_MODELS = [
|
|
|
1632
1647
|
images: 30,
|
|
1633
1648
|
videos: 10,
|
|
1634
1649
|
audios: 10,
|
|
1635
|
-
videoSeconds: { min: 2, max: 30, total: 30, editMin: 4 }
|
|
1650
|
+
videoSeconds: { min: 2, max: 30, total: 30, editMin: 4 },
|
|
1651
|
+
// BytePlus: 2-30 s per clip, 30 s in all, wav/mp3, 15 MB each.
|
|
1652
|
+
audioSeconds: { min: 2, max: 30, total: 30 },
|
|
1653
|
+
audioFormats: ["wav", "mp3"],
|
|
1654
|
+
audioMaxBytes: 15 * 1024 * 1024
|
|
1636
1655
|
},
|
|
1637
1656
|
sourceVideoSeconds: { min: 2, max: 30, editMin: 4, inReferenceTotal: true },
|
|
1638
1657
|
ratioIgnoredWith: ["firstFrame", "sourceVideo"],
|
|
1639
1658
|
// No seed: 2.x echoes it but does not honour it (two seeded runs differed, 2026-10-01).
|
|
1640
|
-
supports: ["generateAudio", "lastFrame", "watermark", "taskType"],
|
|
1659
|
+
supports: ["generateAudio", "lastFrame", "watermark", "taskType", "videoExtension"],
|
|
1660
|
+
// Extend (probe 2026-10-02, 480p): returns ONLY the added footage, continuing from
|
|
1661
|
+
// the source's last frame at the source's framing and fps but at the requested
|
|
1662
|
+
// resolution, so the caller joins the clips. Forward only in practice.
|
|
1663
|
+
extension: { segmentSeconds: 30, maxTotalSeconds: 60, returns: "new" },
|
|
1641
1664
|
prompt: SEEDANCE_PROMPT,
|
|
1642
1665
|
audio: "optional"
|
|
1643
1666
|
},
|
|
@@ -1654,7 +1677,7 @@ var VIDEO_MODELS = [
|
|
|
1654
1677
|
logo: BYTEDANCE_LOGO,
|
|
1655
1678
|
description: "ByteDance Seedance 2.5 for long-form storytelling and omni-reference generation/editing; up to 50 reference assets and 4-30 second clips at up to 1080p."
|
|
1656
1679
|
},
|
|
1657
|
-
capabilities: { acceptsSourceVideo: true, ...FRAMES_EXCLUDE_REFERENCES }
|
|
1680
|
+
capabilities: { acceptsSourceVideo: true, videoEdit: true, ...FRAMES_EXCLUDE_REFERENCES }
|
|
1658
1681
|
},
|
|
1659
1682
|
{
|
|
1660
1683
|
id: "grok-imagine",
|
|
@@ -2018,11 +2041,116 @@ var VIDEO_MODELS = [
|
|
|
2018
2041
|
}
|
|
2019
2042
|
];
|
|
2020
2043
|
|
|
2044
|
+
// src/catalog/video-tools.ts
|
|
2045
|
+
var TOPAZ_LOGO = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.97.0/light/topazlabs.png";
|
|
2046
|
+
var TOPAZ_SOURCE_SECONDS = { max: 300 };
|
|
2047
|
+
var TOPAZ_USAGE = { unit: "seconds" };
|
|
2048
|
+
var VIDEO_TOOL_MODELS = [
|
|
2049
|
+
{
|
|
2050
|
+
id: "topaz-precision",
|
|
2051
|
+
aliases: [],
|
|
2052
|
+
label: "Topaz Precision",
|
|
2053
|
+
type: "video-tool",
|
|
2054
|
+
adapter: "fal",
|
|
2055
|
+
providerModelId: "topaz/upscale/video/precision",
|
|
2056
|
+
lifecycle: "active",
|
|
2057
|
+
exposure: ["cowork"],
|
|
2058
|
+
constraints: {
|
|
2059
|
+
supports: [],
|
|
2060
|
+
sourceVideoSeconds: TOPAZ_SOURCE_SECONDS,
|
|
2061
|
+
tool: { kind: "upscale", upscaleFactor: { min: 1, max: 4 }, maxOutputLongEdge: 3840 }
|
|
2062
|
+
},
|
|
2063
|
+
defaults: {},
|
|
2064
|
+
providerOptions: { fal: { model: "Proteus" } },
|
|
2065
|
+
usage: TOPAZ_USAGE,
|
|
2066
|
+
pricingKind: "video_per_second",
|
|
2067
|
+
display: {
|
|
2068
|
+
provider: "topaz",
|
|
2069
|
+
logo: TOPAZ_LOGO,
|
|
2070
|
+
description: "Topaz Proteus upscaling: sharper, cleaner footage up to 4K while staying faithful to the source.",
|
|
2071
|
+
sortOrder: 20
|
|
2072
|
+
}
|
|
2073
|
+
},
|
|
2074
|
+
{
|
|
2075
|
+
id: "topaz-starlight",
|
|
2076
|
+
aliases: [],
|
|
2077
|
+
label: "Topaz Starlight",
|
|
2078
|
+
type: "video-tool",
|
|
2079
|
+
adapter: "fal",
|
|
2080
|
+
providerModelId: "topaz/upscale/video/generative",
|
|
2081
|
+
lifecycle: "active",
|
|
2082
|
+
exposure: ["cowork"],
|
|
2083
|
+
constraints: {
|
|
2084
|
+
supports: [],
|
|
2085
|
+
sourceVideoSeconds: TOPAZ_SOURCE_SECONDS,
|
|
2086
|
+
tool: { kind: "upscale", upscaleFactor: { min: 1, max: 4 }, maxOutputLongEdge: 3840 }
|
|
2087
|
+
},
|
|
2088
|
+
defaults: {},
|
|
2089
|
+
providerOptions: { fal: { model: "Starlight Precise 2.6" } },
|
|
2090
|
+
usage: TOPAZ_USAGE,
|
|
2091
|
+
pricingKind: "video_per_second",
|
|
2092
|
+
display: {
|
|
2093
|
+
provider: "topaz",
|
|
2094
|
+
logo: TOPAZ_LOGO,
|
|
2095
|
+
description: "Topaz Starlight Precise 2.6: generative upscaling that rebuilds fine detail, up to 4K. Slower and costlier than Precision.",
|
|
2096
|
+
sortOrder: 10
|
|
2097
|
+
}
|
|
2098
|
+
},
|
|
2099
|
+
{
|
|
2100
|
+
id: "topaz-apollo",
|
|
2101
|
+
aliases: [],
|
|
2102
|
+
label: "Topaz Apollo",
|
|
2103
|
+
type: "video-tool",
|
|
2104
|
+
adapter: "fal",
|
|
2105
|
+
providerModelId: "topaz/interpolate/video",
|
|
2106
|
+
lifecycle: "active",
|
|
2107
|
+
exposure: ["cowork"],
|
|
2108
|
+
constraints: {
|
|
2109
|
+
supports: [],
|
|
2110
|
+
sourceVideoSeconds: TOPAZ_SOURCE_SECONDS,
|
|
2111
|
+
tool: { kind: "interpolate", fps: { min: 16, max: 120 } }
|
|
2112
|
+
},
|
|
2113
|
+
defaults: {},
|
|
2114
|
+
providerOptions: { fal: { model: "Apollo" } },
|
|
2115
|
+
usage: TOPAZ_USAGE,
|
|
2116
|
+
pricingKind: "video_per_second",
|
|
2117
|
+
display: {
|
|
2118
|
+
provider: "topaz",
|
|
2119
|
+
logo: TOPAZ_LOGO,
|
|
2120
|
+
description: "Topaz Apollo frame interpolation: smoother motion at up to 120 fps, same length as the source."
|
|
2121
|
+
}
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
id: "topaz-hyperion-2.5",
|
|
2125
|
+
aliases: [],
|
|
2126
|
+
label: "Topaz Hyperion 2.5",
|
|
2127
|
+
type: "video-tool",
|
|
2128
|
+
adapter: "fal",
|
|
2129
|
+
providerModelId: "topaz/sdr-to-hdr/video",
|
|
2130
|
+
lifecycle: "active",
|
|
2131
|
+
exposure: ["cowork"],
|
|
2132
|
+
constraints: {
|
|
2133
|
+
supports: [],
|
|
2134
|
+
sourceVideoSeconds: TOPAZ_SOURCE_SECONDS,
|
|
2135
|
+
tool: { kind: "hdr", outputFormats: ["h265", "prores"] }
|
|
2136
|
+
},
|
|
2137
|
+
defaults: {},
|
|
2138
|
+
usage: TOPAZ_USAGE,
|
|
2139
|
+
pricingKind: "video_per_second",
|
|
2140
|
+
display: {
|
|
2141
|
+
provider: "topaz",
|
|
2142
|
+
logo: TOPAZ_LOGO,
|
|
2143
|
+
description: "Topaz Hyperion 2.5 SDR to HDR: a 10-bit HDR master in H.265 (HDR10) or ProRes 422 HQ, at the source's size and frame rate."
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
];
|
|
2147
|
+
|
|
2021
2148
|
// src/catalog/models.ts
|
|
2022
2149
|
var MODELS = [
|
|
2023
2150
|
...TEXT_MODELS,
|
|
2024
2151
|
...IMAGE_MODELS,
|
|
2025
2152
|
...VIDEO_MODELS,
|
|
2153
|
+
...VIDEO_TOOL_MODELS,
|
|
2026
2154
|
...OTHER_MODELS
|
|
2027
2155
|
];
|
|
2028
2156
|
|
|
@@ -2328,7 +2456,15 @@ var ADAPTER_IDS = [
|
|
|
2328
2456
|
"higgsfield",
|
|
2329
2457
|
"xai-video"
|
|
2330
2458
|
];
|
|
2331
|
-
var ModelTypeSchema = import_zod3.z.enum([
|
|
2459
|
+
var ModelTypeSchema = import_zod3.z.enum([
|
|
2460
|
+
"text",
|
|
2461
|
+
"image",
|
|
2462
|
+
"video",
|
|
2463
|
+
"audio",
|
|
2464
|
+
"upscale",
|
|
2465
|
+
"bg-remove",
|
|
2466
|
+
"video-tool"
|
|
2467
|
+
]);
|
|
2332
2468
|
var LifecycleSchema = import_zod3.z.enum(["active", "deprecated", "retired"]);
|
|
2333
2469
|
var SurfaceSchema = import_zod3.z.enum(["cowork", "space_builder"]);
|
|
2334
2470
|
var SURFACES2 = SurfaceSchema.options;
|
|
@@ -2408,7 +2544,22 @@ var ReferenceLimitsSchema = import_zod3.z.looseObject({
|
|
|
2408
2544
|
audios: import_zod3.z.number().optional(),
|
|
2409
2545
|
total: import_zod3.z.number().optional(),
|
|
2410
2546
|
audioOnly: import_zod3.z.boolean().optional(),
|
|
2411
|
-
videoSeconds: VideoSecondsLimitsSchema.optional()
|
|
2547
|
+
videoSeconds: VideoSecondsLimitsSchema.optional(),
|
|
2548
|
+
audioSeconds: import_zod3.z.looseObject({
|
|
2549
|
+
min: import_zod3.z.number().optional(),
|
|
2550
|
+
max: import_zod3.z.number().optional(),
|
|
2551
|
+
total: import_zod3.z.number().optional()
|
|
2552
|
+
}).optional(),
|
|
2553
|
+
audioFormats: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
2554
|
+
audioMaxBytes: import_zod3.z.number().positive().optional()
|
|
2555
|
+
});
|
|
2556
|
+
var RangeSchema = import_zod3.z.looseObject({ min: import_zod3.z.number(), max: import_zod3.z.number() });
|
|
2557
|
+
var VideoToolConstraintsSchema = import_zod3.z.looseObject({
|
|
2558
|
+
kind: import_zod3.z.enum(["upscale", "interpolate", "hdr"]),
|
|
2559
|
+
upscaleFactor: RangeSchema.optional(),
|
|
2560
|
+
maxOutputLongEdge: import_zod3.z.number().positive().optional(),
|
|
2561
|
+
fps: RangeSchema.optional(),
|
|
2562
|
+
outputFormats: import_zod3.z.array(import_zod3.z.enum(["h265", "prores"])).min(1).optional()
|
|
2412
2563
|
});
|
|
2413
2564
|
var ModelConstraintsSchema = import_zod3.z.looseObject({
|
|
2414
2565
|
aspectRatios: import_zod3.z.array(AspectRatioSchema).optional(),
|
|
@@ -2423,7 +2574,14 @@ var ModelConstraintsSchema = import_zod3.z.looseObject({
|
|
|
2423
2574
|
supports: import_zod3.z.array(SupportedOptionSchema).optional(),
|
|
2424
2575
|
ratioIgnoredWith: import_zod3.z.array(import_zod3.z.enum(["firstFrame", "sourceVideo"])).optional(),
|
|
2425
2576
|
sourceVideoSeconds: VideoSecondsLimitsSchema.omit({ total: true }).extend({ inReferenceTotal: import_zod3.z.boolean().optional() }).optional(),
|
|
2426
|
-
|
|
2577
|
+
// segmentSeconds/maxTotalSeconds stay required: a kit before 0.3.0 drops a row whose
|
|
2578
|
+
// extension lacks them, so every extension block keeps them.
|
|
2579
|
+
extension: import_zod3.z.looseObject({
|
|
2580
|
+
segmentSeconds: import_zod3.z.number(),
|
|
2581
|
+
maxTotalSeconds: import_zod3.z.number(),
|
|
2582
|
+
returns: import_zod3.z.enum(["new", "joined"]).optional()
|
|
2583
|
+
}).optional(),
|
|
2584
|
+
tool: VideoToolConstraintsSchema.optional(),
|
|
2427
2585
|
// Loose on purpose: the read path keeps a row whose value is invalid (the accessors in
|
|
2428
2586
|
// conventions.ts read it as "not set"); CatalogModelSchema below rejects it on write.
|
|
2429
2587
|
audio: import_zod3.z.unknown().optional(),
|
|
@@ -2495,6 +2653,7 @@ var CatalogDisplaySchema = import_zod3.z.looseObject({
|
|
|
2495
2653
|
});
|
|
2496
2654
|
var CatalogCapabilitiesSchema = import_zod3.z.looseObject({
|
|
2497
2655
|
acceptsSourceVideo: import_zod3.z.boolean().optional(),
|
|
2656
|
+
videoEdit: import_zod3.z.boolean().optional(),
|
|
2498
2657
|
framesExcludeReferences: import_zod3.z.boolean().optional(),
|
|
2499
2658
|
lastFrameRequiresFirstFrame: import_zod3.z.boolean().optional(),
|
|
2500
2659
|
reasoning: import_zod3.z.boolean().optional(),
|
|
@@ -2537,6 +2696,13 @@ var CatalogModelSchemaBase = import_zod3.z.looseObject({
|
|
|
2537
2696
|
message: "active text rows need contextWindow"
|
|
2538
2697
|
});
|
|
2539
2698
|
}
|
|
2699
|
+
if (row.type === "video-tool" && row.constraints.tool === void 0) {
|
|
2700
|
+
ctx.addIssue({
|
|
2701
|
+
code: "custom",
|
|
2702
|
+
path: ["constraints", "tool"],
|
|
2703
|
+
message: "video-tool rows need constraints.tool"
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2540
2706
|
if (row.replacedBy !== void 0 && row.replacedBy === row.id) {
|
|
2541
2707
|
ctx.addIssue({
|
|
2542
2708
|
code: "custom",
|
|
@@ -2842,6 +3008,114 @@ function resetLiveCatalog() {
|
|
|
2842
3008
|
liveCatalog.replace([...MODELS]);
|
|
2843
3009
|
}
|
|
2844
3010
|
|
|
3011
|
+
// src/catalog/video-tools-spec.ts
|
|
3012
|
+
var import_zod6 = require("zod");
|
|
3013
|
+
var RangeSchema2 = import_zod6.z.object({ min: import_zod6.z.number(), max: import_zod6.z.number() });
|
|
3014
|
+
var ToolSchema = import_zod6.z.object({
|
|
3015
|
+
kind: import_zod6.z.enum(["upscale", "interpolate", "hdr"]),
|
|
3016
|
+
upscaleFactor: RangeSchema2.optional(),
|
|
3017
|
+
maxOutputLongEdge: import_zod6.z.number().positive().optional(),
|
|
3018
|
+
fps: RangeSchema2.optional(),
|
|
3019
|
+
outputFormats: import_zod6.z.array(import_zod6.z.enum(["h265", "prores"])).min(1).optional()
|
|
3020
|
+
});
|
|
3021
|
+
function videoToolConstraints(row) {
|
|
3022
|
+
if (row.type !== "video-tool") return void 0;
|
|
3023
|
+
const parsed = ToolSchema.safeParse(row.constraints.tool);
|
|
3024
|
+
if (!parsed.success) return void 0;
|
|
3025
|
+
const { kind, upscaleFactor, maxOutputLongEdge, fps, outputFormats } = parsed.data;
|
|
3026
|
+
return {
|
|
3027
|
+
kind,
|
|
3028
|
+
...upscaleFactor !== void 0 && { upscaleFactor },
|
|
3029
|
+
...maxOutputLongEdge !== void 0 && { maxOutputLongEdge },
|
|
3030
|
+
...fps !== void 0 && { fps },
|
|
3031
|
+
...outputFormats !== void 0 && { outputFormats }
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
function resolutionTier(width, height) {
|
|
3035
|
+
const shortEdge = Math.min(width, height);
|
|
3036
|
+
if (shortEdge <= 360) return "360p";
|
|
3037
|
+
if (shortEdge <= 480) return "480p";
|
|
3038
|
+
if (shortEdge <= 720) return "720p";
|
|
3039
|
+
if (shortEdge <= 1080) return "1080p";
|
|
3040
|
+
if (shortEdge <= 1440) return "2k";
|
|
3041
|
+
return "4k";
|
|
3042
|
+
}
|
|
3043
|
+
function fail(error) {
|
|
3044
|
+
return { ok: false, error };
|
|
3045
|
+
}
|
|
3046
|
+
function planVideoTool(row, req) {
|
|
3047
|
+
const tool = videoToolConstraints(row);
|
|
3048
|
+
if (!tool) return fail(`${row.id} is not a video tool`);
|
|
3049
|
+
const { width, height, durationSeconds, fps } = req.source;
|
|
3050
|
+
if (!(width > 0 && height > 0 && durationSeconds > 0)) {
|
|
3051
|
+
return fail("The source video's width, height and duration are required");
|
|
3052
|
+
}
|
|
3053
|
+
const maxSeconds = row.constraints.sourceVideoSeconds?.max;
|
|
3054
|
+
if (maxSeconds !== void 0 && durationSeconds > maxSeconds) {
|
|
3055
|
+
return fail(`${row.label} takes source videos up to ${maxSeconds} seconds.`);
|
|
3056
|
+
}
|
|
3057
|
+
const base = {
|
|
3058
|
+
durationSeconds,
|
|
3059
|
+
...fps !== void 0 && { fps }
|
|
3060
|
+
};
|
|
3061
|
+
if (tool.kind === "upscale") {
|
|
3062
|
+
const factor = req.upscaleFactor;
|
|
3063
|
+
if (factor === void 0) return fail("An upscale factor is required");
|
|
3064
|
+
const range = tool.upscaleFactor;
|
|
3065
|
+
if (range && (factor < range.min || factor > range.max)) {
|
|
3066
|
+
return fail(`${row.label} upscales by ${range.min}x to ${range.max}x.`);
|
|
3067
|
+
}
|
|
3068
|
+
const outWidth = Math.round(width * factor);
|
|
3069
|
+
const outHeight = Math.round(height * factor);
|
|
3070
|
+
const limit = tool.maxOutputLongEdge;
|
|
3071
|
+
if (limit !== void 0 && Math.max(outWidth, outHeight) > limit) {
|
|
3072
|
+
return fail(
|
|
3073
|
+
`A ${factor}x upscale of a ${width}x${height} video passes the ${limit}px limit.`
|
|
3074
|
+
);
|
|
3075
|
+
}
|
|
3076
|
+
return {
|
|
3077
|
+
ok: true,
|
|
3078
|
+
output: {
|
|
3079
|
+
...base,
|
|
3080
|
+
width: outWidth,
|
|
3081
|
+
height: outHeight,
|
|
3082
|
+
resolution: resolutionTier(outWidth, outHeight),
|
|
3083
|
+
format: "h264"
|
|
3084
|
+
}
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
if (tool.kind === "interpolate") {
|
|
3088
|
+
const target = req.targetFps;
|
|
3089
|
+
if (target === void 0 || !Number.isInteger(target)) {
|
|
3090
|
+
return fail("A whole-number target frame rate is required");
|
|
3091
|
+
}
|
|
3092
|
+
const range = tool.fps;
|
|
3093
|
+
if (range && (target < range.min || target > range.max)) {
|
|
3094
|
+
return fail(`${row.label} outputs ${range.min} to ${range.max} fps.`);
|
|
3095
|
+
}
|
|
3096
|
+
return {
|
|
3097
|
+
ok: true,
|
|
3098
|
+
output: {
|
|
3099
|
+
durationSeconds,
|
|
3100
|
+
fps: target,
|
|
3101
|
+
width,
|
|
3102
|
+
height,
|
|
3103
|
+
resolution: resolutionTier(width, height),
|
|
3104
|
+
format: "h264"
|
|
3105
|
+
}
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
const formats = tool.outputFormats ?? ["h265"];
|
|
3109
|
+
const format = req.outputFormat ?? formats[0] ?? "h265";
|
|
3110
|
+
if (!formats.includes(format)) {
|
|
3111
|
+
return fail(`${row.label} writes ${formats.join(" or ")}.`);
|
|
3112
|
+
}
|
|
3113
|
+
return {
|
|
3114
|
+
ok: true,
|
|
3115
|
+
output: { ...base, width, height, resolution: resolutionTier(width, height), format }
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
|
|
2845
3119
|
// src/resolve-options.ts
|
|
2846
3120
|
var SIZE_ORDER = ["512", "1K", "2K", "4K"];
|
|
2847
3121
|
var RESOLUTION_ORDER = ["360p", "480p", "720p", "768p", "1080p", "2k", "4k"];
|