@hypit/hypit 0.2.6 → 0.2.8
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/README.md +1 -1
- package/dist/public/hyperframes.d.ts +19 -2
- package/examples/provider-package/README.md +2 -2
- package/package.json +2 -1
- package/packages/caption-fine/README.md +4 -4
- package/packages/credential-store-file/README.md +4 -0
- package/packages/credential-store-file/src/store.ts +2 -2
- package/packages/credential-store-platform/README.md +2 -0
- package/packages/hyperframes/README.md +7 -0
- package/packages/hyperframes/src/html-project.ts +99 -0
- package/packages/hyperframes/src/index.ts +2 -0
- package/packages/hyperframes/src/project.ts +28 -0
- package/packages/pixverse/README.md +35 -0
- package/packages/pixverse/package.json +21 -0
- package/packages/pixverse/src/activation.ts +11 -0
- package/packages/pixverse/src/index.ts +122 -0
- package/packages/pixverse/src/surface.ts +146 -0
- package/packages/provider-hiapi/README.md +1 -1
- package/packages/provider-hyperframes-local/README.md +19 -7
- package/packages/provider-hyperframes-local/src/capture.ts +18 -8
- package/packages/provider-hyperframes-local/src/index.ts +1 -1
- package/packages/provider-hyperframes-local/src/output.ts +1 -1
- package/packages/provider-hyperframes-local/src/process-tree.ts +8 -2
- package/packages/provider-hyperframes-local/src/provider.ts +29 -2
- package/packages/provider-hyperframes-local/src/render.ts +45 -20
- package/packages/provider-hyperframes-local/src/sampling.ts +3 -2
- package/packages/provider-hypihub/README.md +3 -4
- package/packages/provider-monid/README.md +1 -1
- package/packages/provider-tokendance/README.md +1 -1
- package/packages/provider-whisperx-local/README.md +8 -5
- package/packages/render-hyperframes/README.md +9 -0
- package/packages/render-hyperframes/src/index.ts +3 -0
- package/packages/render-hyperframes/src/manifest.ts +7 -2
- package/packages/render-hyperframes/src/product.ts +41 -2
- package/packages/runtime-local/src/process-control.ts +4 -1
- package/packages/runtime-local/src/programs.ts +15 -3
- package/packages/runtime-local/src/supervisor.ts +8 -1
- package/packages/script/README.md +11 -4
- package/packages/script/src/format.ts +11 -24
- package/packages/script/src/manifest.ts +2 -2
- package/packages/script/src/parser.ts +16 -21
- package/packages/script/src/surface.ts +5 -3
- package/packages/script/src/types.ts +1 -1
- package/packages/seedance/README.md +11 -18
- package/packages/seedance/src/index.ts +7 -7
- package/packages/seedance/src/surface.ts +9 -8
- package/packages/seedance-kits/README.md +4 -3
- package/packages/source/README.md +1 -1
- package/packages/source/src/header.ts +2 -1
- package/packages/studio/README.md +13 -0
- package/packages/studio/src/feedback-server.ts +7 -0
- package/packages/studio/src/mutation-origin.ts +20 -0
- package/packages/studio/src/parameters.ts +3 -7
- package/packages/studio/src/preview/render.ts +7 -1
- package/packages/studio/src/server.ts +39 -0
- package/packages/studio/src/session.ts +7 -3
- package/packages/studio/src/ui/syntax.ts +11 -10
- package/packages/temporal-markup/EDITING.md +1 -1
- package/packages/video-cli/README.md +47 -4
- package/packages/video-cli/package.json +3 -0
- package/packages/video-cli/src/cli.ts +9 -9
- package/packages/video-cli/src/creation.ts +3 -3
- package/packages/video-cli/src/frame-grid.ts +34 -0
- package/packages/video-cli/src/index.ts +4 -0
- package/packages/video-cli/src/media-frames.ts +25 -0
- package/packages/video-cli/src/media.ts +198 -46
- package/packages/video-cli/src/process.ts +13 -3
- package/packages/video-cli/src/secret-input.ts +14 -0
- package/packages/video-cli/src/snapshot.ts +186 -0
- package/packages/yt-dlp/src/download.ts +32 -18
package/README.md
CHANGED
|
@@ -788,5 +788,22 @@ type BrowserProgram = {
|
|
|
788
788
|
};
|
|
789
789
|
declare function browserProgram(value: BrowserProgram, artifacts?: readonly BlobRef[]): VisualProgramElement["program"];
|
|
790
790
|
|
|
791
|
-
|
|
792
|
-
|
|
791
|
+
/** An already materialized HTML programme and the local files its URLs address. */
|
|
792
|
+
type HyperframesHtmlProject = {
|
|
793
|
+
readonly html: string;
|
|
794
|
+
readonly assets: readonly {
|
|
795
|
+
readonly url: string;
|
|
796
|
+
readonly artifact: BlobRef;
|
|
797
|
+
}[];
|
|
798
|
+
};
|
|
799
|
+
/** Read the exact clock emitted by this package. Seconds never reconstruct frame identity. */
|
|
800
|
+
declare function hyperframesHtmlDomain(html: string): HyperframesFrameDomain & {
|
|
801
|
+
readonly canvas: HyperframesCanvas;
|
|
802
|
+
};
|
|
803
|
+
/** Resource URL positions in the compiler's self-contained HTML (inline JS and CSS). */
|
|
804
|
+
declare function mapHyperframesHtmlUrls(html: string, map: (url: string) => string): string;
|
|
805
|
+
declare function hyperframesHtmlAssetUrls(html: string): readonly string[];
|
|
806
|
+
declare function assertHyperframesHtmlProject(value: unknown): asserts value is HyperframesHtmlProject;
|
|
807
|
+
|
|
808
|
+
export { BROWSER_PROGRAM_FORMAT, assertHyperframesDocument, assertHyperframesFrameIndex, assertHyperframesFrameSpan, assertHyperframesHtmlProject, browserProgram, compileHyperframesDocument, hyperframesComponent, hyperframesDocumentFragment, hyperframesDocumentSchema, hyperframesHtmlAssetUrls, hyperframesHtmlDomain, hyperframesManifest, hyperframesModuleRef, hyperframesProducers, hyperframesResourceUri, hyperframesTime, hyperframesTypes, mapHyperframesHtmlUrls, materializeHyperframesHtml };
|
|
809
|
+
export type { BrowserProgram, HyperframesCanvas, HyperframesDocument, HyperframesFrameDomain, HyperframesFrameSpan, HyperframesHtmlProject, ResourceUrlResolver };
|
|
@@ -68,7 +68,7 @@ differ from the image example, and each is the reason the video example exists.
|
|
|
68
68
|
**The reference vocabulary is wider, and each role maps to its own field.** The Model declares
|
|
69
69
|
`referenceImage`, `referenceVideo` and `referenceAudio` as separate ports precisely so each can map
|
|
70
70
|
to one wire field — a service that mixes them into one array cannot tell the roles apart. An image
|
|
71
|
-
reference also
|
|
71
|
+
reference also requires a boolean `personReference` classification, which is why those ports use
|
|
72
72
|
`itemObject` rather than `urlArray`: the item field travels with the URL it belongs to.
|
|
73
73
|
|
|
74
74
|
```json
|
|
@@ -76,7 +76,7 @@ reference also carries an optional `personReference` classification, which is wh
|
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
`firstFrame` and `lastFrame` accept at most one item each, so their body fields are URLs.
|
|
79
|
-
This illustrative service accepts their
|
|
79
|
+
This illustrative service accepts their required visual-reference person classification on `POST /uploads` through
|
|
80
80
|
`x-person-reference: true` or `false`. Their mappings declare `resourceFields: ["personReference"]`,
|
|
81
81
|
and the URL resolver sends that header when supplied. Replace this with the real service’s
|
|
82
82
|
documented transport; a field that the service cannot carry must be refused, never discarded.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypit/hypit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"homepage": "https://hypit.ai",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -232,6 +232,7 @@
|
|
|
232
232
|
"@hypit/narrative": "workspace:*",
|
|
233
233
|
"@hypit/performance": "workspace:*",
|
|
234
234
|
"@hypit/performance-studio": "workspace:*",
|
|
235
|
+
"@hypit/pixverse": "workspace:*",
|
|
235
236
|
"@hypit/program-space": "workspace:*",
|
|
236
237
|
"@hypit/project-context-node": "workspace:*",
|
|
237
238
|
"@hypit/protocol": "workspace:*",
|
|
@@ -14,10 +14,10 @@ does not carry a private visual role.
|
|
|
14
14
|
</caption-fine:Track>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
An optional Spatial Region Timeline
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
An optional Spatial Region Timeline supplies a moving placement point when the user's reference
|
|
18
|
+
visibly uses head-following Caption or the user asks for that treatment. Ordinary Caption uses the
|
|
19
|
+
Style's placement without regions. The Region Timeline is authored numeric input mapped from available picture
|
|
20
|
+
evidence to the composition that consumes it; it is not a face-tracking request:
|
|
21
21
|
|
|
22
22
|
```xml
|
|
23
23
|
<space:RegionTimeline id="heads" within={vertical} recipe={tracking.heads.default}/>
|
|
@@ -50,6 +50,10 @@ The default directory is `credentials` under the Host state root printed by `hyp
|
|
|
50
50
|
`config.path` explicitly selects a directory, including an absolute directory outside that root;
|
|
51
51
|
relative paths resolve against the Host root.
|
|
52
52
|
Opening the adapter and reading an absent key do not create files. Keep this directory outside Git.
|
|
53
|
+
On WSL, the default under the Linux home uses normal Linux permissions. If the Host state root is
|
|
54
|
+
on a Windows-mounted drive, set this Store's `config.path` to a private directory in the WSL Linux
|
|
55
|
+
filesystem before login. A Windows mount's reported mode does not establish that its Windows ACL
|
|
56
|
+
protects the secret, so the Store does not waive its owner-private check for that path.
|
|
53
57
|
|
|
54
58
|
Each key has its own JSON file containing only `secret` and optional `expiresAt`, as defined by
|
|
55
59
|
CredentialValue. Filenames use reversible lower-case hex encoding of the key's UTF-16 bytes, with a
|
|
@@ -38,7 +38,7 @@ export class FileCredentialStore implements WritableCredentialStore {
|
|
|
38
38
|
const info = await stat(this.directory);
|
|
39
39
|
if (!info.isDirectory()) throw new Error(`Credential path is not a directory: ${this.directory}`);
|
|
40
40
|
if (process.platform !== "win32" && (info.mode & 0o077) !== 0) {
|
|
41
|
-
throw new Error(`Credential directory must be owner-private: ${this.directory}
|
|
41
|
+
throw new Error(`Credential directory must be owner-private: ${this.directory}. On WSL, keep credentials in the Linux filesystem rather than a Windows-mounted drive.`);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -57,7 +57,7 @@ export class FileCredentialStore implements WritableCredentialStore {
|
|
|
57
57
|
try {
|
|
58
58
|
const info = await file.stat();
|
|
59
59
|
if (process.platform !== "win32" && (info.mode & 0o077) !== 0) {
|
|
60
|
-
throw new Error(`Credential file must be owner-private: ${path}
|
|
60
|
+
throw new Error(`Credential file must be owner-private: ${path}. On WSL, keep credentials in the Linux filesystem rather than a Windows-mounted drive.`);
|
|
61
61
|
}
|
|
62
62
|
const text = await file.readFile("utf8");
|
|
63
63
|
// JSON parser messages may include secret bytes. Report the file, never its contents.
|
|
@@ -47,6 +47,8 @@ credential stored in a locker stays in that locker; a credential stored in a fil
|
|
|
47
47
|
- `path` selects the directory for Linux file storage, resolved against the Host state root printed by
|
|
48
48
|
`hypit paths`. The default is the same `credentials` directory `@hypit/credential-store-file` uses,
|
|
49
49
|
so on Linux, switching between the two Stores finds the credential already stored.
|
|
50
|
+
- WSL uses the Linux file Store. If the Host state root is on a Windows-mounted drive, select a
|
|
51
|
+
private directory in the WSL Linux filesystem with `path`; the Store keeps its permission check.
|
|
50
52
|
- `service` selects the locker service name. The OS Store's own default applies when it is absent.
|
|
51
53
|
|
|
52
54
|
## Storage on each platform
|
|
@@ -25,6 +25,13 @@ a Provider can verify and name staged bytes without guessing from the hash. A lo
|
|
|
25
25
|
render Runtime calls `materializeHyperframesHtml()` with its own Artifact URL resolver before
|
|
26
26
|
handing the HTML to HyperFrames. That environment-specific materialization is not a new compiled
|
|
27
27
|
Record and does not change the compiled document.
|
|
28
|
+
|
|
29
|
+
For observation of already materialized HTML, `HyperframesHtmlProject` carries the HTML and the
|
|
30
|
+
BlobRefs addressed by its media/font URLs. `hyperframesHtmlDomain` reads the compiler's exact root
|
|
31
|
+
frame clock and canvas; `stageHyperframesHtmlProject` stages those resources. This input does not
|
|
32
|
+
reconstruct a Composition or invent typed Surface facts from HTML. A caller with the original
|
|
33
|
+
`HyperframesDocument`, such as Studio, should pass that document to preserve all declared dependencies,
|
|
34
|
+
including resources embedded in browser-program data. The document remains the complete typed input.
|
|
28
35
|
Staging assigns short local filenames; opaque Resource IDs are never interpreted as filesystem
|
|
29
36
|
paths or filenames, so their length and punctuation do not restrict the host filesystem.
|
|
30
37
|
`stageHyperframesProject` streams each asset into its staged file. Its `validateSurface(surface, path,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { BlobRef } from "@hypit/protocol";
|
|
2
|
+
import { isResourceId } from "@hypit/protocol";
|
|
3
|
+
import type { HyperframesCanvas, HyperframesFrameDomain } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/** An already materialized HTML programme and the local files its URLs address. */
|
|
6
|
+
export type HyperframesHtmlProject = {
|
|
7
|
+
readonly html: string;
|
|
8
|
+
readonly assets: readonly { readonly url: string; readonly artifact: BlobRef }[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Read the exact clock emitted by this package. Seconds never reconstruct frame identity. */
|
|
12
|
+
export function hyperframesHtmlDomain(html: string): HyperframesFrameDomain & { readonly canvas: HyperframesCanvas } {
|
|
13
|
+
const attributeValue = (tag: string, name: string): string | undefined => {
|
|
14
|
+
const marker = `${name}="`;
|
|
15
|
+
for (let at = tag.indexOf(marker); at !== -1; at = tag.indexOf(marker, at + marker.length)) {
|
|
16
|
+
const previous = tag[at - 1];
|
|
17
|
+
if (previous === undefined || !" \t\r\n\f".includes(previous)) continue;
|
|
18
|
+
const start = at + marker.length;
|
|
19
|
+
const end = tag.indexOf('"', start);
|
|
20
|
+
return end === -1 ? undefined : tag.slice(start, end);
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
};
|
|
24
|
+
let root: string | undefined;
|
|
25
|
+
for (let at = html.indexOf("<div"); at !== -1; at = html.indexOf("<div", at)) {
|
|
26
|
+
const end = html.indexOf(">", at + 4);
|
|
27
|
+
if (end === -1) break;
|
|
28
|
+
const tag = html.slice(at, end + 1);
|
|
29
|
+
at = end + 1;
|
|
30
|
+
if (attributeValue(tag, "data-composition-id") === undefined) continue;
|
|
31
|
+
if (root !== undefined) throw new Error("Snapshot HTML needs exactly one compiled HyperFrames composition root");
|
|
32
|
+
root = tag;
|
|
33
|
+
}
|
|
34
|
+
if (root === undefined) throw new Error("Snapshot HTML needs exactly one compiled HyperFrames composition root");
|
|
35
|
+
const attribute = (name: string) => {
|
|
36
|
+
const value = attributeValue(root, name);
|
|
37
|
+
if (value === undefined) throw new Error(`Snapshot HTML lacks ${name}; use the compiled HyperFrames HTML`);
|
|
38
|
+
return value;
|
|
39
|
+
};
|
|
40
|
+
const positive = (value: string, name: string) => {
|
|
41
|
+
const number = Number(value);
|
|
42
|
+
if (!/^\d+$/u.test(value) || !Number.isSafeInteger(number) || number <= 0) throw new Error(`Invalid HyperFrames ${name}`);
|
|
43
|
+
return number;
|
|
44
|
+
};
|
|
45
|
+
const fps = attribute("data-fps").split("/");
|
|
46
|
+
if (fps.length > 2) throw new Error("HyperFrames data-fps must be an exact rational");
|
|
47
|
+
return {
|
|
48
|
+
frameRate: { numerator: positive(fps[0]!, "frame rate"), denominator: positive(fps[1] ?? "1", "frame rate denominator") },
|
|
49
|
+
frameCount: positive(attribute("data-hypit-frame-count"), "frame count"),
|
|
50
|
+
canvas: { width: positive(attribute("data-width"), "width"), height: positive(attribute("data-height"), "height") },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function unescape(value: string): string {
|
|
55
|
+
return value.replaceAll(""", '"').replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Resource URL positions in the compiler's self-contained HTML (inline JS and CSS). */
|
|
59
|
+
export function mapHyperframesHtmlUrls(html: string, map: (url: string) => string): string {
|
|
60
|
+
const escape = (value: string) => value.replaceAll("&", "&").replaceAll('"', """);
|
|
61
|
+
const cssUrls = (css: string) => css.replace(/url\(\s*(["']?)([^)"']+)\1\s*\)/gu,
|
|
62
|
+
(_all, _quote: string, url: string) => `url("${map(url.trim()).replaceAll("\\", "\\\\").replaceAll('"', '\\"')}")`);
|
|
63
|
+
return html.replace(/<style\b([^>]*)>([\s\S]*?)<\/style>/gu,
|
|
64
|
+
(_all, attributes: string, css: string) => `<style${attributes}>${cssUrls(css)}</style>`)
|
|
65
|
+
.replace(/<[a-z][^>]*>/gu, tag => {
|
|
66
|
+
const attributes = /^(?:<(?:img|video|source|image|use|script|link)\b)/u.test(tag)
|
|
67
|
+
? tag.replace(/\s(src|poster|href)="([^"]*)"/gu,
|
|
68
|
+
(_all, key: string, url: string) => ` ${key}="${escape(map(unescape(url)))}"`) : tag;
|
|
69
|
+
return attributes.replace(/\sstyle="([^"]*)"/gu,
|
|
70
|
+
(_all, css: string) => ` style="${escape(cssUrls(unescape(css)))}"`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function hyperframesHtmlAssetUrls(html: string): readonly string[] {
|
|
75
|
+
const urls = new Set<string>();
|
|
76
|
+
mapHyperframesHtmlUrls(html, url => {
|
|
77
|
+
if (url !== "" && !url.startsWith("#") && !url.startsWith("data:")) urls.add(url);
|
|
78
|
+
return url;
|
|
79
|
+
});
|
|
80
|
+
return [...urls];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function assertHyperframesHtmlProject(value: unknown): asserts value is HyperframesHtmlProject {
|
|
84
|
+
const project = value as HyperframesHtmlProject;
|
|
85
|
+
if (!project || typeof project.html !== "string" || !Array.isArray(project.assets)) throw new Error("HyperFrames HTML project needs html and assets");
|
|
86
|
+
hyperframesHtmlDomain(project.html);
|
|
87
|
+
if (/hypit-resource:\/\//u.test(project.html)) throw new Error("HTML still has unresolved resources; materialize its HyperframesDocument first");
|
|
88
|
+
const urls = new Set<string>();
|
|
89
|
+
for (const asset of project.assets) {
|
|
90
|
+
const blob = asset?.artifact;
|
|
91
|
+
if (typeof asset?.url !== "string" || urls.has(asset.url) || blob?.kind !== "blob" || !isResourceId(blob.resource)
|
|
92
|
+
|| !Number.isSafeInteger(blob.size) || blob.size < 0 || typeof blob.mediaType !== "string" || !blob.mediaType) {
|
|
93
|
+
throw new Error("Invalid or repeated HyperFrames HTML asset");
|
|
94
|
+
}
|
|
95
|
+
urls.add(asset.url);
|
|
96
|
+
}
|
|
97
|
+
const referenced = hyperframesHtmlAssetUrls(project.html);
|
|
98
|
+
if (referenced.length !== urls.size || referenced.some(url => !urls.has(url))) throw new Error("HyperFrames HTML assets must address its referenced URLs");
|
|
99
|
+
}
|
|
@@ -11,3 +11,5 @@ export {
|
|
|
11
11
|
export type * from "./types.js";
|
|
12
12
|
export { browserProgram, BROWSER_PROGRAM_FORMAT } from "./browser-program.js";
|
|
13
13
|
export type { BrowserProgram } from "./browser-program.js";
|
|
14
|
+
export { assertHyperframesHtmlProject, hyperframesHtmlDomain, hyperframesHtmlAssetUrls, mapHyperframesHtmlUrls } from "./html-project.js";
|
|
15
|
+
export type { HyperframesHtmlProject } from "./html-project.js";
|
|
@@ -6,6 +6,8 @@ import type { BlobRef } from "@hypit/protocol";
|
|
|
6
6
|
|
|
7
7
|
import { assertHyperframesDocument, materializeHyperframesHtml } from "./document.js";
|
|
8
8
|
import type { HyperframesDocument } from "./types.js";
|
|
9
|
+
import { assertHyperframesHtmlProject, mapHyperframesHtmlUrls } from "./html-project.js";
|
|
10
|
+
import type { HyperframesHtmlProject } from "./html-project.js";
|
|
9
11
|
|
|
10
12
|
/** Reads one execution resource. Whose store it comes from is the caller's business. */
|
|
11
13
|
export type HyperframesArtifactReader = (
|
|
@@ -37,6 +39,32 @@ function extension(mediaType: string): string {
|
|
|
37
39
|
return found;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
/** Stage an existing HTML programme; no reverse compilation or inferred Surface declarations. */
|
|
43
|
+
export async function stageHyperframesHtmlProject(options: {
|
|
44
|
+
readonly project: HyperframesHtmlProject;
|
|
45
|
+
readonly directory: string;
|
|
46
|
+
readonly read: HyperframesArtifactReader;
|
|
47
|
+
readonly signal?: AbortSignal;
|
|
48
|
+
}): Promise<void> {
|
|
49
|
+
assertHyperframesHtmlProject(options.project);
|
|
50
|
+
await mkdir(join(options.directory, "artifacts"), { recursive: true });
|
|
51
|
+
const paths = new Map<string, string>();
|
|
52
|
+
for (const [index, asset] of options.project.assets.entries()) {
|
|
53
|
+
options.signal?.throwIfAborted();
|
|
54
|
+
const path = `artifacts/asset-${index}${extension(asset.artifact.mediaType)}`;
|
|
55
|
+
const bytes = await options.read(asset.artifact, options.signal);
|
|
56
|
+
const file = await open(join(options.directory, path), "w");
|
|
57
|
+
try {
|
|
58
|
+
if (bytes instanceof Uint8Array) await file.writeFile(bytes);
|
|
59
|
+
else for await (const chunk of bytes) { options.signal?.throwIfAborted(); await file.writeFile(chunk); }
|
|
60
|
+
} finally { await file.close(); }
|
|
61
|
+
paths.set(asset.url, `./${path}`);
|
|
62
|
+
}
|
|
63
|
+
await writeFile(join(options.directory, "index.html"), mapHyperframesHtmlUrls(options.project.html, url => paths.get(url) ?? url), {
|
|
64
|
+
encoding: "utf8", ...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
40
68
|
/**
|
|
41
69
|
* Lay one document out as a directory HyperFrames can render.
|
|
42
70
|
*
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# `@hypit/pixverse`
|
|
2
|
+
|
|
3
|
+
Exact author/compute contracts and a package-owned author Surface for PixVerse V6.
|
|
4
|
+
|
|
5
|
+
The Surface projects the primary result to an ordinary video Artifact. The package contains no
|
|
6
|
+
Provider selection, API key or network execution. The selected Provider implements its exact
|
|
7
|
+
capability.
|
|
8
|
+
|
|
9
|
+
Connect prompt and frames as ordinary graph edges:
|
|
10
|
+
|
|
11
|
+
```xml
|
|
12
|
+
<pix:Video
|
|
13
|
+
id="opening"
|
|
14
|
+
prompt={line}
|
|
15
|
+
duration="5"
|
|
16
|
+
quality="720p"
|
|
17
|
+
aspect-ratio="9:16"
|
|
18
|
+
generate-audio="true"
|
|
19
|
+
/>
|
|
20
|
+
|
|
21
|
+
<pix:Video id="bridge" prompt={motion} duration="5" quality="720p"
|
|
22
|
+
first-frame={hero.image} last-frame={product.image}/>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The Surface only lowers this syntax into the package's exact model request. It does not select a
|
|
26
|
+
Provider.
|
|
27
|
+
|
|
28
|
+
The model renders 1 to 15 seconds at `360p`, `540p`, `720p` or `1080p`. A prompt-only run states its
|
|
29
|
+
`aspect-ratio`; a run that starts from a `first-frame` takes that frame's shape instead. A
|
|
30
|
+
`last-frame` bridges from the first frame into one continuous shot, so it is not combined with
|
|
31
|
+
`multi-clip`, which renders the prompt as several cuts.
|
|
32
|
+
|
|
33
|
+
`generate-audio` renders an audio track alongside the picture, including speech the prompt asks a
|
|
34
|
+
character to say. The model exposes no separate voice, language or dialogue field, so a spoken line
|
|
35
|
+
belongs in the prompt itself.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hypit/pixverse",
|
|
3
|
+
"version": "0.0.0-dev",
|
|
4
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
5
|
+
"private": true,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"hypit": {
|
|
11
|
+
"activation": "./src/activation.ts"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@hypit/artifact": "workspace:*",
|
|
15
|
+
"@hypit/generation": "workspace:*",
|
|
16
|
+
"@hypit/markup": "workspace:*",
|
|
17
|
+
"@hypit/model-kit": "workspace:*",
|
|
18
|
+
"@hypit/protocol": "workspace:*",
|
|
19
|
+
"@hypit/text": "workspace:*"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createMarkupSurfaceHostFacet } from "@hypit/markup";
|
|
2
|
+
import {
|
|
3
|
+
pixverseComponent, pixverseDefinition, pixverseManifest, pixverseModuleRef, pixverseMarkupSurfaces,
|
|
4
|
+
} from "./index.js";
|
|
5
|
+
import { decodePixverseVideoSurface } from "./surface.js";
|
|
6
|
+
export const hypitPackage = { format: "hypit.node-package@1" as const, modules: [{ manifest: pixverseManifest }], components: [pixverseComponent], hostFacets: [
|
|
7
|
+
pixverseDefinition.hostFacet,
|
|
8
|
+
createMarkupSurfaceHostFacet({ module: pixverseModuleRef,
|
|
9
|
+
declaration: pixverseMarkupSurfaces.find((item) => item.name === "video")!, handler: decodePixverseVideoSurface }),
|
|
10
|
+
] };
|
|
11
|
+
export default hypitPackage;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { artifactTypes } from "@hypit/artifact";
|
|
2
|
+
import { sealGenerationPortRequest, sealGenerationPortTable } from "@hypit/generation";
|
|
3
|
+
import type { GenerationPortTable, GenerationPortValue, GenerationRequest } from "@hypit/generation";
|
|
4
|
+
import type { SurfaceAttributeVocabulary } from "@hypit/markup";
|
|
5
|
+
import { defineExactModelModule } from "@hypit/model-kit";
|
|
6
|
+
import { textTypes } from "@hypit/text";
|
|
7
|
+
|
|
8
|
+
export const pixverseModuleRef = { name: "@hypit/pixverse", version: "1" } as const;
|
|
9
|
+
export const pixverseModels = ["pixverse-v6"] as const;
|
|
10
|
+
export type PixverseModel = typeof pixverseModels[number];
|
|
11
|
+
|
|
12
|
+
const PIXVERSE_QUALITIES = ["360p", "540p", "720p", "1080p"] as const;
|
|
13
|
+
const PIXVERSE_ASPECT_RATIOS = ["16:9", "4:3", "1:1", "3:4", "9:16", "2:3", "3:2", "21:9"] as const;
|
|
14
|
+
|
|
15
|
+
export const pixverseV6Ports: GenerationPortTable = sealGenerationPortTable({
|
|
16
|
+
model: "pixverse-v6",
|
|
17
|
+
result: "video",
|
|
18
|
+
ports: [
|
|
19
|
+
{ name: "prompt", value: { kind: "text", maxChars: 5_000 }, minItems: 1, maxItems: 1 },
|
|
20
|
+
{ name: "firstFrame", value: { kind: "media", accepts: ["image"] }, minItems: 0, maxItems: 1 },
|
|
21
|
+
{ name: "lastFrame", value: { kind: "media", accepts: ["image"] }, minItems: 0, maxItems: 1 },
|
|
22
|
+
{ name: "duration", value: { kind: "number", integer: true, minimum: 1, maximum: 15 }, minItems: 1, maxItems: 1 },
|
|
23
|
+
{ name: "quality", value: { kind: "enum", values: [...PIXVERSE_QUALITIES] }, minItems: 1, maxItems: 1 },
|
|
24
|
+
{ name: "aspectRatio", value: { kind: "enum", values: [...PIXVERSE_ASPECT_RATIOS] }, minItems: 0, maxItems: 1 },
|
|
25
|
+
{ name: "generateAudio", value: { kind: "boolean" }, minItems: 0, maxItems: 1 },
|
|
26
|
+
{ name: "multiClip", value: { kind: "boolean" }, minItems: 0, maxItems: 1 },
|
|
27
|
+
{ name: "seed", value: { kind: "number", integer: true, minimum: 0, maximum: 2_147_483_647 }, minItems: 0, maxItems: 1 },
|
|
28
|
+
],
|
|
29
|
+
requires: [
|
|
30
|
+
// A last frame states where a run that already has a first frame ends.
|
|
31
|
+
{ kind: "requiresPresent", port: "lastFrame", needs: ["firstFrame"] },
|
|
32
|
+
// A run that starts from a frame inherits that frame's shape, and one that
|
|
33
|
+
// bridges two frames renders a single continuous shot.
|
|
34
|
+
{ kind: "atMostOneOf", ports: ["aspectRatio", "firstFrame"] },
|
|
35
|
+
{ kind: "atMostOneOf", ports: ["multiClip", "lastFrame"] },
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const pixversePorts: Readonly<Record<PixverseModel, GenerationPortTable>> = {
|
|
40
|
+
"pixverse-v6": pixverseV6Ports,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function sealPixverseRequest(
|
|
44
|
+
ports: Readonly<Record<string, readonly GenerationPortValue[]>>,
|
|
45
|
+
): GenerationRequest {
|
|
46
|
+
return sealGenerationPortRequest(pixverseV6Ports, ports);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const pixverseBaseDefinition = defineExactModelModule({
|
|
50
|
+
module: pixverseModuleRef,
|
|
51
|
+
endpoints: [{
|
|
52
|
+
key: "video",
|
|
53
|
+
requestTypeName: "PixverseV6Request",
|
|
54
|
+
producerName: "request-pixverse-v6",
|
|
55
|
+
ports: pixverseV6Ports,
|
|
56
|
+
}],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const pixverseEndpoints = pixverseBaseDefinition.endpoints;
|
|
60
|
+
export const pixverseComponent = pixverseBaseDefinition.component;
|
|
61
|
+
const endpoint = pixverseEndpoints.video!;
|
|
62
|
+
|
|
63
|
+
const pixverseAttributes: readonly SurfaceAttributeVocabulary[] = [
|
|
64
|
+
{ name: "id", kind: "identifier", required: true,
|
|
65
|
+
summary: "Names this generation so its video Artifact can be referenced elsewhere in the Source." },
|
|
66
|
+
{ name: "prompt", kind: "reference", required: true, accepts: [textTypes.text],
|
|
67
|
+
summary: "The Text edge describing the shot, including any spoken line the model should voice." },
|
|
68
|
+
{ name: "first-frame", kind: "reference", required: false, accepts: [artifactTypes.blob],
|
|
69
|
+
summary: "Starts the video from one image Artifact." },
|
|
70
|
+
{ name: "last-frame", kind: "reference", required: false, accepts: [artifactTypes.blob],
|
|
71
|
+
summary: "Ends the video on one image Artifact, bridging from the first frame." },
|
|
72
|
+
{ name: "duration", kind: "literal", required: true,
|
|
73
|
+
summary: "How many seconds of video to render, from 1 to 15." },
|
|
74
|
+
{ name: "quality", kind: "literal", required: true, values: [...PIXVERSE_QUALITIES],
|
|
75
|
+
summary: "The size band the model renders at." },
|
|
76
|
+
{ name: "aspect-ratio", kind: "literal", required: false, values: [...PIXVERSE_ASPECT_RATIOS],
|
|
77
|
+
summary: "The Frame shape of the generated video." },
|
|
78
|
+
{ name: "generate-audio", kind: "literal", required: false, values: ["true", "false"],
|
|
79
|
+
summary: "Renders an audio track alongside the picture." },
|
|
80
|
+
{ name: "multi-clip", kind: "literal", required: false, values: ["true", "false"],
|
|
81
|
+
summary: "Renders the prompt as several cuts instead of one continuous shot." },
|
|
82
|
+
{ name: "seed", kind: "literal", required: false,
|
|
83
|
+
summary: "Seeds the model's sampling so a rerun stays close to this one." },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
export const pixverseMarkupSurfaces = [{
|
|
87
|
+
name: "video",
|
|
88
|
+
tag: "Video",
|
|
89
|
+
mode: "structured" as const,
|
|
90
|
+
outputs: [endpoint.draftType, endpoint.mediaBindings.firstFrame!.type, endpoint.mediaBindings.lastFrame!.type],
|
|
91
|
+
vocabulary: {
|
|
92
|
+
summary: "Generates one video with the exact PixVerse V6 model from a Text prompt, optionally starting from a frame or bridging two.",
|
|
93
|
+
attributes: pixverseAttributes,
|
|
94
|
+
ports: [{
|
|
95
|
+
name: "video",
|
|
96
|
+
type: artifactTypes.blob,
|
|
97
|
+
summary: "The generated video, addressed as `<id>.video`.",
|
|
98
|
+
}],
|
|
99
|
+
example: `<pix:Video
|
|
100
|
+
id="opening"
|
|
101
|
+
prompt={line}
|
|
102
|
+
duration="5"
|
|
103
|
+
quality="720p"
|
|
104
|
+
aspect-ratio="9:16"
|
|
105
|
+
generate-audio="true"
|
|
106
|
+
/>`,
|
|
107
|
+
notes: [
|
|
108
|
+
"A run that starts from a frame takes its shape from that frame, so `aspect-ratio` states the shape only for a prompt-only run.",
|
|
109
|
+
"A `last-frame` bridges from the `first-frame` into one continuous shot, so it is not combined with `multi-clip`.",
|
|
110
|
+
"A spoken line belongs in the prompt; the model exposes no separate voice, language or dialogue field.",
|
|
111
|
+
"The Surface lowers the element into the package's exact model request and selects no Provider.",
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
}] as const;
|
|
115
|
+
|
|
116
|
+
export const pixverseManifest = {
|
|
117
|
+
...pixverseBaseDefinition.manifest,
|
|
118
|
+
};
|
|
119
|
+
export const pixverseDefinition = {
|
|
120
|
+
...pixverseBaseDefinition,
|
|
121
|
+
manifest: pixverseManifest,
|
|
122
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { artifactTypes } from "@hypit/artifact";
|
|
2
|
+
import { generationPort, sealGenerationMediaBinding, sealGenerationRequestDraft } from "@hypit/generation";
|
|
3
|
+
import type { GenerationMediaPort, GenerationPortValue } from "@hypit/generation";
|
|
4
|
+
import {
|
|
5
|
+
createExactModelPrimaryGenerationFragment,
|
|
6
|
+
exactModelMediaInputNames,
|
|
7
|
+
exactModelTextInputName,
|
|
8
|
+
} from "@hypit/model-kit";
|
|
9
|
+
import type { ExactModelEndpoint } from "@hypit/model-kit";
|
|
10
|
+
import type {
|
|
11
|
+
MarkupAttributeValue,
|
|
12
|
+
StructuredElement,
|
|
13
|
+
StructuredSurfaceHandler,
|
|
14
|
+
SurfaceResolvedReference,
|
|
15
|
+
} from "@hypit/markup";
|
|
16
|
+
import type { CanonicalValue, TypeRef } from "@hypit/protocol";
|
|
17
|
+
import { textTypes, verifyText } from "@hypit/text";
|
|
18
|
+
|
|
19
|
+
import { pixverseEndpoints } from "./index.js";
|
|
20
|
+
|
|
21
|
+
function assert(condition: unknown, message: string): asserts condition {
|
|
22
|
+
if (!condition) throw new Error(message);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sameType(left: TypeRef, right: TypeRef): boolean {
|
|
26
|
+
return left.name === right.name && left.module.name === right.module.name && left.module.version === right.module.version;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const attributes = ["id", "prompt", "first-frame", "last-frame", "duration", "quality",
|
|
30
|
+
"aspect-ratio", "generate-audio", "multi-clip", "seed"] as const;
|
|
31
|
+
|
|
32
|
+
function exact(element: StructuredElement): void {
|
|
33
|
+
const unknown = Object.keys(element.attributes).filter((name) => !attributes.includes(name as typeof attributes[number]));
|
|
34
|
+
assert(unknown.length === 0, `${element.name} does not accept ${unknown[0]}`);
|
|
35
|
+
const missing = ["id", "prompt", "duration", "quality"].filter((name) => element.attributes[name] === undefined);
|
|
36
|
+
assert(missing.length === 0, `${element.name} requires ${missing.join(", ")}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function text(element: StructuredElement, name: string): string {
|
|
40
|
+
const value = element.attributes[name];
|
|
41
|
+
assert(typeof value === "string" && value.trim().length > 0, `${element.name}.${name} must be text`);
|
|
42
|
+
return value.trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** An absent optional attribute leaves its port unstated, which the model reads as its own default. */
|
|
46
|
+
function optionalText(element: StructuredElement, name: string): readonly GenerationPortValue[] | undefined {
|
|
47
|
+
return element.attributes[name] === undefined ? undefined : [text(element, name)];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function optionalFlag(element: StructuredElement, name: string): readonly GenerationPortValue[] | undefined {
|
|
51
|
+
if (element.attributes[name] === undefined) return undefined;
|
|
52
|
+
const value = text(element, name);
|
|
53
|
+
assert(value === "true" || value === "false", `${element.name}.${name} must be true or false`);
|
|
54
|
+
return [value === "true"];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function integer(element: StructuredElement, name: string): readonly GenerationPortValue[] {
|
|
58
|
+
const value = text(element, name);
|
|
59
|
+
assert(/^\d+$/u.test(value), `${element.name}.${name} must be a whole number`);
|
|
60
|
+
return [Number(value)];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ref(
|
|
64
|
+
element: StructuredElement,
|
|
65
|
+
name: string,
|
|
66
|
+
type: TypeRef,
|
|
67
|
+
resolve: (path: string) => SurfaceResolvedReference | undefined,
|
|
68
|
+
): SurfaceResolvedReference {
|
|
69
|
+
const value: MarkupAttributeValue | undefined = element.attributes[name];
|
|
70
|
+
assert(typeof value === "object" && value.kind === "reference", `${element.name}.${name} must be a reference`);
|
|
71
|
+
const result = resolve(value.path);
|
|
72
|
+
assert(result !== undefined && sameType(result.type, type), `${element.name}.${name} has the wrong type`);
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function frame(
|
|
77
|
+
element: StructuredElement,
|
|
78
|
+
name: string,
|
|
79
|
+
resolve: (path: string) => SurfaceResolvedReference | undefined,
|
|
80
|
+
): SurfaceResolvedReference | undefined {
|
|
81
|
+
if (element.attributes[name] === undefined) return undefined;
|
|
82
|
+
const image = ref(element, name, artifactTypes.blob, resolve);
|
|
83
|
+
if (image.record !== undefined) {
|
|
84
|
+
assert(image.record.value.kind === "blob" && image.record.value.mediaType.startsWith("image/"),
|
|
85
|
+
`${element.name}.${name} must reference image media`);
|
|
86
|
+
}
|
|
87
|
+
return image;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function decoder(endpoint: ExactModelEndpoint): StructuredSurfaceHandler {
|
|
91
|
+
return ({ element, resolveReference }) => {
|
|
92
|
+
exact(element);
|
|
93
|
+
assert(!element.children.some((item) => item.kind === "element" || item.value.trim()),
|
|
94
|
+
`${element.name} accepts no children`);
|
|
95
|
+
const id = text(element, "id");
|
|
96
|
+
const prompt = ref(element, "prompt", textTypes.text, resolveReference);
|
|
97
|
+
if (prompt.record !== undefined) {
|
|
98
|
+
assert(prompt.record.value.kind === "inline", `${element.name}.prompt must reference Text`);
|
|
99
|
+
verifyText(prompt.record.value.value);
|
|
100
|
+
}
|
|
101
|
+
const frames = ([["firstFrame", "first-frame"], ["lastFrame", "last-frame"]] as const)
|
|
102
|
+
.flatMap(([port, attribute]) => {
|
|
103
|
+
const image = frame(element, attribute, resolveReference);
|
|
104
|
+
return image === undefined ? [] : [{ port, image }];
|
|
105
|
+
});
|
|
106
|
+
const stated: Record<string, readonly GenerationPortValue[] | undefined> = {
|
|
107
|
+
duration: integer(element, "duration"),
|
|
108
|
+
quality: [text(element, "quality")],
|
|
109
|
+
aspectRatio: optionalText(element, "aspect-ratio"),
|
|
110
|
+
generateAudio: optionalFlag(element, "generate-audio"),
|
|
111
|
+
multiClip: optionalFlag(element, "multi-clip"),
|
|
112
|
+
seed: element.attributes["seed"] === undefined ? undefined : integer(element, "seed"),
|
|
113
|
+
};
|
|
114
|
+
const draft = sealGenerationRequestDraft(endpoint.ports, Object.fromEntries(
|
|
115
|
+
Object.entries(stated).filter((entry): entry is [string, readonly GenerationPortValue[]] => entry[1] !== undefined),
|
|
116
|
+
));
|
|
117
|
+
const records: Array<{ id: string; type: TypeRef; value: { kind: "inline"; value: CanonicalValue }; range: StructuredElement["range"] }> = [{
|
|
118
|
+
id: `${id}.draft`, type: endpoint.draftType,
|
|
119
|
+
value: { kind: "inline", value: draft as unknown as CanonicalValue }, range: element.range,
|
|
120
|
+
}];
|
|
121
|
+
const inputs: Record<string, SurfaceResolvedReference["ref"] | { kind: "record"; id: string }> = {
|
|
122
|
+
draft: { kind: "record", id: `${id}.draft` }, [exactModelTextInputName("prompt")]: prompt.ref,
|
|
123
|
+
};
|
|
124
|
+
const media = frames.map(({ port, image }, index) => {
|
|
125
|
+
const name = `media-${String(index + 1).padStart(4, "0")}`;
|
|
126
|
+
const bindingId = `${id}.${name}.binding`;
|
|
127
|
+
const mediaPort = generationPort(endpoint.ports, port);
|
|
128
|
+
assert(mediaPort.value.kind === "media", `PixVerse port ${port} is not media`);
|
|
129
|
+
records.push({
|
|
130
|
+
id: bindingId, type: endpoint.mediaBindings[port]!.type,
|
|
131
|
+
value: { kind: "inline", value: sealGenerationMediaBinding(mediaPort as GenerationMediaPort, { role: "image" }) as unknown as CanonicalValue },
|
|
132
|
+
range: element.range,
|
|
133
|
+
});
|
|
134
|
+
const names = exactModelMediaInputNames(name);
|
|
135
|
+
inputs[names.binding] = { kind: "record", id: bindingId };
|
|
136
|
+
inputs[names.artifact] = image.ref;
|
|
137
|
+
return { name, port } as const;
|
|
138
|
+
});
|
|
139
|
+
const fragment = createExactModelPrimaryGenerationFragment(endpoint, media, [{ name: "prompt", port: "prompt" }]);
|
|
140
|
+
return { records, fragments: [fragment], components: [{
|
|
141
|
+
id, fragment: fragment.id, inputs, outputs: { video: `${id}.video` }, range: element.range,
|
|
142
|
+
}] };
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const decodePixverseVideoSurface = decoder(pixverseEndpoints.video!);
|
|
@@ -37,7 +37,7 @@ Service limits this Provider reports as unsupported before submitting:
|
|
|
37
37
|
`1:3`, `9:21`; 4K excludes `1:1`, `3:1`, `1:3`, `9:21`; image-to-image takes up to six references.
|
|
38
38
|
- Grok Imagine renders 480p or 720p; `grok-imagine-1.5/image-to-video` animates exactly one image.
|
|
39
39
|
|
|
40
|
-
Seedance visual references
|
|
40
|
+
Seedance visual references require `person-reference`; the Provider accepts the declaration and
|
|
41
41
|
transmits nothing for it, since HiAPI has no field for it. Seedance rejects reference images and
|
|
42
42
|
videos that contain a real human face; HiAPI offers no way to register authorized portrait material,
|
|
43
43
|
so such a request fails with the service's moderation error.
|