@hypit/hypit 0.2.5 → 0.2.7
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 +6 -1
- package/dist/public/hyperframes.d.ts +19 -2
- package/package.json +3 -1
- package/packages/caption/src/temporalize.ts +11 -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-os/runtime/windows-credential.ps1 +3 -0
- package/packages/credential-store-os/src/windows.ts +14 -0
- 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/media-execution/src/execute.ts +21 -3
- 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 +9 -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 +10 -2
- package/packages/provider-monid/README.md +29 -9
- package/packages/provider-monid/package.json +2 -1
- package/packages/provider-monid/src/mapping.ts +24 -0
- package/packages/provider-monid/src/provider.ts +24 -11
- package/packages/provider-monid/src/routes.ts +25 -3
- package/packages/provider-pollo/README.md +9 -1
- package/packages/provider-tokendance/README.md +9 -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/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/wan/README.md +31 -0
- package/packages/wan/package.json +21 -0
- package/packages/wan/src/activation.ts +11 -0
- package/packages/wan/src/index.ts +160 -0
- package/packages/wan/src/surface.ts +147 -0
- package/packages/yt-dlp/src/download.ts +32 -18
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"@hypit/minimax-h3": "workspace:*",
|
|
13
13
|
"@hypit/protocol": "workspace:*",
|
|
14
14
|
"@hypit/runtime": "workspace:*",
|
|
15
|
-
"@hypit/runtime-kit": "workspace:*"
|
|
15
|
+
"@hypit/runtime-kit": "workspace:*",
|
|
16
|
+
"@hypit/wan": "workspace:*"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"@hypit/seedance": "workspace:*"
|
|
@@ -3,6 +3,7 @@ import type { GenerationWireMapping } from "@hypit/generation";
|
|
|
3
3
|
|
|
4
4
|
const SEEDANCE: ModuleRef = { name: "@hypit/seedance", version: "1" };
|
|
5
5
|
const MINIMAX_H3: ModuleRef = { name: "@hypit/minimax-h3", version: "1" };
|
|
6
|
+
const WAN: ModuleRef = { name: "@hypit/wan", version: "1" };
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* One mapping plus the Monid provider that relays the endpoint. Monid addresses an endpoint by
|
|
@@ -57,10 +58,33 @@ const minimaxH3: MonidMapping = {
|
|
|
57
58
|
},
|
|
58
59
|
};
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Monid's `alibaba` Wan 2.7 image endpoints. Both variants take the same fields; the Pro variant
|
|
63
|
+
* adds a 4K band. Input images are plain public URLs rather than role-tagged items, and the model
|
|
64
|
+
* reads exclusions from the prompt, so it has no negative-prompt field.
|
|
65
|
+
*/
|
|
66
|
+
const wan = (name: string, endpoint: string): MonidMapping => ({
|
|
67
|
+
service: "alibaba",
|
|
68
|
+
capability: { module: WAN, name }, result: "image",
|
|
69
|
+
routes: [{ model: endpoint }],
|
|
70
|
+
fields: {
|
|
71
|
+
prompt: { as: "value", field: "prompt" },
|
|
72
|
+
images: { as: "urlArray", field: "images" },
|
|
73
|
+
resolution: { as: "value", field: "size" },
|
|
74
|
+
count: { as: "value", field: "n" },
|
|
75
|
+
imageSet: { as: "value", field: "enable_sequential" },
|
|
76
|
+
extendedReasoning: { as: "value", field: "thinking_mode" },
|
|
77
|
+
watermark: { as: "value", field: "watermark" },
|
|
78
|
+
seed: { as: "value", field: "seed" },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
60
82
|
export const monidMappings: readonly MonidMapping[] = [
|
|
61
83
|
seedance("seedance-2", "/v1/video/seedance-2.0"),
|
|
62
84
|
seedance("seedance-2-fast", "/v1/video/seedance-2.0-fast"),
|
|
63
85
|
seedance("seedance-2-mini", "/v1/video/seedance-2.0-mini"),
|
|
64
86
|
seedance("seedance-2.5", "/v1/video/seedance-2.5"),
|
|
65
87
|
minimaxH3,
|
|
88
|
+
wan("wan-2.7-image", "/v1/image/wan2.7-image"),
|
|
89
|
+
wan("wan-2.7-image-pro", "/v1/image/wan2.7-image-pro"),
|
|
66
90
|
];
|
|
@@ -31,7 +31,7 @@ type Handle = {
|
|
|
31
31
|
readonly runId: string;
|
|
32
32
|
readonly route: string;
|
|
33
33
|
readonly startedAt: number;
|
|
34
|
-
readonly
|
|
34
|
+
readonly urls?: readonly string[];
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
function assert(condition: unknown, message: string): asserts condition { if (!condition) throw new Error(message); }
|
|
@@ -66,13 +66,21 @@ function httpsUrl(value: unknown, subject: string): string {
|
|
|
66
66
|
return value;
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* The relayed result. The ModelArk endpoints return the task's `video_url
|
|
70
|
-
*
|
|
69
|
+
* The relayed result. The ModelArk endpoints return the task's `video_url` and MiniMax-H3 its
|
|
70
|
+
* `content.url`; an image endpoint returns one entry per rendered picture. Every link expires, so
|
|
71
|
+
* collection downloads them rather than storing the addresses.
|
|
71
72
|
*/
|
|
72
|
-
function
|
|
73
|
+
function outputUrls(run: Record<string, unknown>): readonly string[] {
|
|
73
74
|
const output = object(run.output, "Monid run output");
|
|
74
75
|
const content = output.content === undefined ? undefined : object(output.content, "Monid run output content");
|
|
75
|
-
|
|
76
|
+
const listed = [output.results, output.images, output.urls, content?.results, content?.images]
|
|
77
|
+
.find((value): value is readonly unknown[] => Array.isArray(value) && value.length > 0);
|
|
78
|
+
if (listed !== undefined) {
|
|
79
|
+
return listed.map((item, index) => httpsUrl(
|
|
80
|
+
typeof item === "string" ? item : object(item, `Monid run output ${index}`).url,
|
|
81
|
+
`Monid run output ${index}`));
|
|
82
|
+
}
|
|
83
|
+
return [httpsUrl(output.video_url ?? output.url ?? content?.video_url ?? content?.url, "Monid run output")];
|
|
76
84
|
}
|
|
77
85
|
const extensions: Readonly<Record<string, string>> = {
|
|
78
86
|
"image/jpeg": "jpg", "image/png": "png", "image/webp": "webp", "video/mp4": "mp4", "video/quicktime": "mov",
|
|
@@ -180,7 +188,7 @@ function endpoint(client: MonidClient, pollIntervalMs: number, maxOperationMs: n
|
|
|
180
188
|
if (!ended) return { ...wakeAfter(canonicalize(handle), pollIntervalMs, Date.now(), { phase: String(run.status) }), receipt };
|
|
181
189
|
const rejected = monidRunFailure(run, handle.runId);
|
|
182
190
|
if (rejected !== undefined) return { ...failure(rejected), receipt };
|
|
183
|
-
return { status: "ready", handle: canonicalize({ ...handle,
|
|
191
|
+
return { status: "ready", handle: canonicalize({ ...handle, urls: outputUrls(run) }), receipt };
|
|
184
192
|
} catch (error) {
|
|
185
193
|
return failure(error);
|
|
186
194
|
}
|
|
@@ -201,7 +209,7 @@ function endpoint(client: MonidClient, pollIntervalMs: number, maxOperationMs: n
|
|
|
201
209
|
}
|
|
202
210
|
const rejected = monidRunFailure(run, handle.runId);
|
|
203
211
|
if (rejected !== undefined) return { ...failure(rejected), receipt };
|
|
204
|
-
return { status: "ready", handle: canonicalize({ ...handle,
|
|
212
|
+
return { status: "ready", handle: canonicalize({ ...handle, urls: outputUrls(run) }), receipt };
|
|
205
213
|
} catch (error) {
|
|
206
214
|
return failure(error);
|
|
207
215
|
}
|
|
@@ -211,10 +219,15 @@ function endpoint(client: MonidClient, pollIntervalMs: number, maxOperationMs: n
|
|
|
211
219
|
const handle = object(context.handle, "Monid handle") as unknown as Handle;
|
|
212
220
|
const route = monidRouteForCapability(context.need.capability);
|
|
213
221
|
assert(route !== undefined && handle.route === route.key, "Monid collection route differs");
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
222
|
+
const urls = handle.urls ?? [];
|
|
223
|
+
assert(urls.length > 0, "Monid handle carries no result");
|
|
224
|
+
await context.reportProgress?.({ phase: `Receiving ${urls.length} generated ${route.media}${urls.length === 1 ? "" : "s"}` });
|
|
225
|
+
const blobs: BlobRef[] = [];
|
|
226
|
+
for (const url of urls) {
|
|
227
|
+
const downloaded = await client.download(httpsUrl(url, "Monid handle"));
|
|
228
|
+
blobs.push(await context.resources.put(downloaded.bytes, downloaded.mediaType));
|
|
229
|
+
}
|
|
230
|
+
return { status: "completed", result: { value: route.packageResult(blobs) }, receipt: { id: handle.runId } };
|
|
218
231
|
} catch (error) {
|
|
219
232
|
return failure(error);
|
|
220
233
|
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
compileWireRequest,
|
|
3
3
|
selectWireModelForRequest,
|
|
4
4
|
generationTypes,
|
|
5
|
+
sealGeneratedImageSet,
|
|
5
6
|
sealGeneratedVideoSet,
|
|
6
7
|
} from "@hypit/generation";
|
|
7
8
|
import type { GenerationArtifactUrlResolver, GenerationRequest } from "@hypit/generation";
|
|
@@ -22,6 +23,8 @@ export type MonidPreparedRequest = {
|
|
|
22
23
|
export type MonidRoute = MonidMapping & {
|
|
23
24
|
readonly key: string;
|
|
24
25
|
readonly returns: TypeRef;
|
|
26
|
+
/** What the run produces, which decides how many results collection expects. */
|
|
27
|
+
readonly media: "image" | "video";
|
|
25
28
|
readonly supports: (request: EndpointRequest) => EndpointSupport;
|
|
26
29
|
readonly prepare: (constraints: CanonicalValue) => MonidPreparedRequest;
|
|
27
30
|
readonly packageResult: (artifacts: readonly BlobRef[]) => StoredValue;
|
|
@@ -42,7 +45,20 @@ function referenceToVideo(request: GenerationRequest): boolean {
|
|
|
42
45
|
return present(request, "referenceImage") || present(request, "referenceVideo") || present(request, "referenceAudio");
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
function wanRejection(mapping: MonidMapping, request: GenerationRequest): string | undefined {
|
|
49
|
+
const set = scalar(request, "imageSet") === true;
|
|
50
|
+
if (scalar(request, "resolution") === "4K" && (present(request, "images") || set)) {
|
|
51
|
+
return "Wan renders 4K from a prompt alone, without reference images or an image set";
|
|
52
|
+
}
|
|
53
|
+
const count = scalar(request, "count");
|
|
54
|
+
if (typeof count === "number" && count > 4 && !set) {
|
|
55
|
+
return `Wan renders at most 4 pictures outside an image set, not ${count}`;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
45
60
|
function rejection(mapping: MonidMapping, request: GenerationRequest): string | undefined {
|
|
61
|
+
if (mapping.capability.module.name === "@hypit/wan") return wanRejection(mapping, request);
|
|
46
62
|
if (mapping.capability.name === "minimax-h3") {
|
|
47
63
|
// MiniMax-H3 resolves the framing from the uploaded image in frame mode and defaults
|
|
48
64
|
// reference-to-video to adaptive. Text-to-video carries no such source, and the endpoint
|
|
@@ -103,7 +119,8 @@ function capabilityKey(capability: CapabilityRef): string {
|
|
|
103
119
|
export const monidRoutes: readonly MonidRoute[] = monidMappings.map((mapping) => ({
|
|
104
120
|
...mapping,
|
|
105
121
|
key: capabilityKey(mapping.capability),
|
|
106
|
-
returns: generationTypes.videoSet,
|
|
122
|
+
returns: mapping.result === "image" ? generationTypes.imageSet : generationTypes.videoSet,
|
|
123
|
+
media: mapping.result === "image" ? "image" as const : "video" as const,
|
|
107
124
|
supports: (request) => {
|
|
108
125
|
const reason = rejection(mapping, request.constraints as unknown as GenerationRequest);
|
|
109
126
|
return reason === undefined ? { status: "supported" } : { status: "unsupported", reason };
|
|
@@ -112,7 +129,10 @@ export const monidRoutes: readonly MonidRoute[] = monidMappings.map((mapping) =>
|
|
|
112
129
|
const request = constraints as unknown as GenerationRequest;
|
|
113
130
|
const reason = rejection(mapping, request);
|
|
114
131
|
if (reason !== undefined) throw new Error(reason);
|
|
115
|
-
|
|
132
|
+
// The Wan endpoints take the compiled fields as their body; the ModelArk endpoints fold their
|
|
133
|
+
// media into one role-tagged `content` array first.
|
|
134
|
+
const body = mapping.capability.module.name === "@hypit/wan" ? (input: Record<string, unknown>) => input
|
|
135
|
+
: mapping.capability.name === "minimax-h3" ? minimaxH3Input : arkInput;
|
|
116
136
|
return {
|
|
117
137
|
service: mapping.service,
|
|
118
138
|
endpoint: selectWireModelForRequest(mapping, request),
|
|
@@ -121,7 +141,9 @@ export const monidRoutes: readonly MonidRoute[] = monidMappings.map((mapping) =>
|
|
|
121
141
|
},
|
|
122
142
|
packageResult: (artifacts) => ({
|
|
123
143
|
kind: "inline",
|
|
124
|
-
value: canonicalize(
|
|
144
|
+
value: canonicalize(mapping.result === "image"
|
|
145
|
+
? sealGeneratedImageSet({ images: artifacts })
|
|
146
|
+
: sealGeneratedVideoSet({ videos: artifacts })),
|
|
125
147
|
}),
|
|
126
148
|
}));
|
|
127
149
|
|
|
@@ -37,6 +37,13 @@ Runtime Profile example:
|
|
|
37
37
|
|
|
38
38
|
```json
|
|
39
39
|
{
|
|
40
|
+
"format": "hypit.runtime-local@1",
|
|
41
|
+
"dataRoot": ".hypit/runtimes/local",
|
|
42
|
+
"credentials": {
|
|
43
|
+
"platform": {
|
|
44
|
+
"use": "@hypit/credential-store-platform"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
40
47
|
"endpoints": {
|
|
41
48
|
"pollo.default": {
|
|
42
49
|
"use": "@hypit/provider-pollo",
|
|
@@ -47,7 +54,8 @@ Runtime Profile example:
|
|
|
47
54
|
"pollIntervalMs": 10000
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
|
-
}
|
|
57
|
+
},
|
|
58
|
+
"bindings": {}
|
|
51
59
|
}
|
|
52
60
|
```
|
|
53
61
|
|
|
@@ -46,6 +46,13 @@ Runtime Profile example:
|
|
|
46
46
|
|
|
47
47
|
```json
|
|
48
48
|
{
|
|
49
|
+
"format": "hypit.runtime-local@1",
|
|
50
|
+
"dataRoot": ".hypit/runtimes/local",
|
|
51
|
+
"credentials": {
|
|
52
|
+
"platform": {
|
|
53
|
+
"use": "@hypit/credential-store-platform"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
49
56
|
"endpoints": {
|
|
50
57
|
"tokendance.default": {
|
|
51
58
|
"use": "@hypit/provider-tokendance",
|
|
@@ -56,7 +63,8 @@ Runtime Profile example:
|
|
|
56
63
|
"pollIntervalMs": 10000
|
|
57
64
|
}
|
|
58
65
|
}
|
|
59
|
-
}
|
|
66
|
+
},
|
|
67
|
+
"bindings": {}
|
|
60
68
|
}
|
|
61
69
|
```
|
|
62
70
|
|
|
@@ -91,16 +91,19 @@ torch caches and their environment settings. Changing a running service's cache
|
|
|
91
91
|
an explicit restart when idle, just like changing its model or hardware. The managed installation
|
|
92
92
|
does not require a global `whisperx` shell command.
|
|
93
93
|
|
|
94
|
-
Preparation and service processes inherit the environment of the command starting them. Set
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
Preparation and service processes inherit the environment of the command starting them. Set download
|
|
95
|
+
route variables on `programs prepare` (or the preparation performed by `programs up` / `runtime up`).
|
|
96
|
+
Changing only a download source does not require restarting a running service using the same cache.
|
|
97
|
+
Cache settings must select the same resource location for preparation and the service. A running
|
|
98
|
+
service retains its earlier environment; arrange an idle restart when changing that location or its
|
|
99
|
+
inference settings. Inspect its reported configuration and logs, and account for active work.
|
|
98
100
|
|
|
99
101
|
If NLTK refuses a proxied fetch during preparation, follow the service’s
|
|
100
102
|
[explicit proxy preparation](../../services/whisperx/README.md#preparing-sentence-data-through-a-proxy).
|
|
101
103
|
|
|
102
104
|
Preparation commands write `install.log`; the running service writes `program.log`, with stderr in
|
|
103
|
-
`program.err.log` on Windows. Inspect
|
|
105
|
+
`program.err.log` on Windows. Inspect service logs for Python model-loading and inference messages;
|
|
106
|
+
download messages belong to preparation's `install.log`.
|
|
104
107
|
`programs status` reports these files as `installationLogPath`, `logPath` and `errorLogPath` when they
|
|
105
108
|
exist, even before installation finishes. Preparation notices name the Python environment and selected model/language resources separately. The service logs the start and completion of ASR loading, transcription,
|
|
106
109
|
language-alignment model loading and alignment, with elapsed times. Downloads happen only in
|
|
@@ -6,6 +6,15 @@ semantic coverage through the same frame and audio pipeline.
|
|
|
6
6
|
|
|
7
7
|
Explicit author and capability boundary for final HyperFrames video rendering.
|
|
8
8
|
|
|
9
|
+
`@hypit/render-hyperframes@1#render-frames` captures an existing programme's selected frames.
|
|
10
|
+
`hyperframesFramesRequest({ document, frames })` accepts a `HyperframesDocument` and strictly
|
|
11
|
+
increasing original-frame indices. `{ project: { html, assets }, frames }` accepts already
|
|
12
|
+
materialized HTML with its URL-addressed assets. The `Frames` result is a PNG `BlobRef[]` in request
|
|
13
|
+
order; the request already owns the frame identities. Canvas and clock stay in the input.
|
|
14
|
+
This capability requests picture capture only. File names, contact sheets and pagination belong to
|
|
15
|
+
the caller. `hypit snapshot` invokes it immediately through a selected Profile; there is no Build
|
|
16
|
+
or video encoding in that observation path.
|
|
17
|
+
|
|
9
18
|
The package owns `<render:Video composition={...}/>` and lowers it to ordinary Operations that:
|
|
10
19
|
|
|
11
20
|
1. obtain the `ProgramSpace` from the Timeline input and compile the referenced `Composition` into a `HyperframesDocument`;
|
|
@@ -4,3 +4,6 @@ export { renderHyperframesCapabilities, renderHyperframesManifest, renderHyperfr
|
|
|
4
4
|
export { hyperframesVisualRequest, verifyHyperframesVisualRequest } from "./product.js";
|
|
5
5
|
export type { HyperframesVisualRequest } from "./product.js";
|
|
6
6
|
export { decodeHyperframesRenderSurface } from "./surface.js";
|
|
7
|
+
export { renderHyperframesTypes } from "./manifest.js";
|
|
8
|
+
export { hyperframesFramesRequest, hyperframesFramesDomain, verifyHyperframesFramesRequest, verifyHyperframesFrames } from "./product.js";
|
|
9
|
+
export type { HyperframesFramesRequest, HyperframesFrames } from "./product.js";
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
CapabilityRef,
|
|
13
13
|
ModuleManifest,
|
|
14
14
|
ProducerRef,
|
|
15
|
+
TypeRef,
|
|
15
16
|
} from "@hypit/protocol";
|
|
16
17
|
import {
|
|
17
18
|
mediaPipelineModuleRef,
|
|
@@ -20,7 +21,11 @@ import {
|
|
|
20
21
|
export const renderHyperframesModuleRef = { name: "@hypit/render-hyperframes", version: "1" } as const;
|
|
21
22
|
export const renderHyperframesCapabilities = {
|
|
22
23
|
renderVisual: { module: renderHyperframesModuleRef, name: "render-visual" },
|
|
24
|
+
renderFrames: { module: renderHyperframesModuleRef, name: "render-frames" },
|
|
23
25
|
} satisfies Record<string, CapabilityRef>;
|
|
26
|
+
export const renderHyperframesTypes = {
|
|
27
|
+
frames: { module: renderHyperframesModuleRef, name: "Frames" },
|
|
28
|
+
} satisfies Record<string, TypeRef>;
|
|
24
29
|
export const renderHyperframesProducers = {
|
|
25
30
|
requestVisualRange: { module: renderHyperframesModuleRef, name: "request-visual-range" },
|
|
26
31
|
requestVisual: { module: renderHyperframesModuleRef, name: "request-visual-render" },
|
|
@@ -70,11 +75,11 @@ export const renderHyperframesManifest: ModuleManifest = {
|
|
|
70
75
|
{ module: hyperframesModuleRef },
|
|
71
76
|
{ module: mediaPipelineModuleRef },
|
|
72
77
|
],
|
|
73
|
-
types: [],
|
|
78
|
+
types: [{ name: renderHyperframesTypes.frames.name }],
|
|
74
79
|
capabilities: [{
|
|
75
80
|
name: renderHyperframesCapabilities.renderVisual.name,
|
|
76
81
|
returns: mediaTypes.renderedVisual,
|
|
77
|
-
}],
|
|
82
|
+
}, { name: renderHyperframesCapabilities.renderFrames.name, returns: renderHyperframesTypes.frames }],
|
|
78
83
|
producers: [
|
|
79
84
|
{
|
|
80
85
|
name: renderHyperframesProducers.requestVisual.name,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CanonicalValue } from "@hypit/protocol";
|
|
2
|
-
import { assertHyperframesDocument } from "@hypit/hyperframes";
|
|
3
|
-
import type { HyperframesDocument } from "@hypit/hyperframes";
|
|
2
|
+
import { assertHyperframesDocument, assertHyperframesHtmlProject, hyperframesHtmlDomain } from "@hypit/hyperframes";
|
|
3
|
+
import type { HyperframesDocument, HyperframesHtmlProject } from "@hypit/hyperframes";
|
|
4
|
+
import type { BlobRef } from "@hypit/protocol";
|
|
5
|
+
import { isResourceId } from "@hypit/protocol";
|
|
4
6
|
import { verifyMediaFrameRange } from "@hypit/media";
|
|
5
7
|
import type { MediaFrameRange } from "@hypit/media";
|
|
6
8
|
|
|
@@ -9,6 +11,43 @@ export type HyperframesVisualRequest = {
|
|
|
9
11
|
readonly range?: MediaFrameRange;
|
|
10
12
|
};
|
|
11
13
|
|
|
14
|
+
/** Select original programme frames, in strictly increasing order. */
|
|
15
|
+
export type HyperframesFramesRequest = ({ readonly document: HyperframesDocument } | { readonly project: HyperframesHtmlProject }) & {
|
|
16
|
+
readonly frames: readonly number[];
|
|
17
|
+
};
|
|
18
|
+
/** PNG images in request order. The request already supplies each frame's identity. */
|
|
19
|
+
export type HyperframesFrames = readonly BlobRef[];
|
|
20
|
+
|
|
21
|
+
export function hyperframesFramesDomain(request: HyperframesFramesRequest) {
|
|
22
|
+
return "document" in request ? request.document : hyperframesHtmlDomain(request.project.html);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function verifyHyperframesFramesRequest(value: unknown): asserts value is HyperframesFramesRequest {
|
|
26
|
+
if (!value || typeof value !== "object" || Array.isArray(value)
|
|
27
|
+
|| Object.keys(value).some(key => !["document", "project", "frames"].includes(key))
|
|
28
|
+
|| ("document" in value) === ("project" in value)) throw new Error("Frame request needs exactly one document or HTML project, and frames");
|
|
29
|
+
const request = value as HyperframesFramesRequest;
|
|
30
|
+
if ("document" in request) assertHyperframesDocument(request.document);
|
|
31
|
+
else assertHyperframesHtmlProject(request.project);
|
|
32
|
+
const { frameCount } = hyperframesFramesDomain(request);
|
|
33
|
+
if (!Array.isArray(request.frames) || request.frames.length === 0 || request.frames.some((frame, index) =>
|
|
34
|
+
!Number.isSafeInteger(frame) || frame < 0 || frame >= frameCount || (index > 0 && frame <= request.frames[index - 1]!))) {
|
|
35
|
+
throw new Error(`Frames must be strictly increasing integers in [0, ${frameCount})`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function hyperframesFramesRequest(request: HyperframesFramesRequest): CanonicalValue {
|
|
40
|
+
verifyHyperframesFramesRequest(request);
|
|
41
|
+
return request as unknown as CanonicalValue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function verifyHyperframesFrames(value: unknown): asserts value is HyperframesFrames {
|
|
45
|
+
if (!Array.isArray(value) || value.length === 0 || value.some(blob => !blob || blob.kind !== "blob"
|
|
46
|
+
|| !isResourceId(blob.resource) || blob.mediaType !== "image/png" || !Number.isSafeInteger(blob.size) || blob.size <= 0)) {
|
|
47
|
+
throw new Error("HyperFrames frames must be PNG BlobRefs in request order");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
12
51
|
export function verifyHyperframesVisualRequest(value: unknown): asserts value is HyperframesVisualRequest {
|
|
13
52
|
if (value === null || typeof value !== "object" || Array.isArray(value)
|
|
14
53
|
|| Object.keys(value).some((key) => key !== "document" && key !== "range")) {
|
|
@@ -23,7 +23,10 @@ function signal(pid: number, force: boolean): ProcessStopResult {
|
|
|
23
23
|
process.kill(target, name);
|
|
24
24
|
return "sent";
|
|
25
25
|
} catch (error) {
|
|
26
|
-
if (errorCode(error) === "EPERM")
|
|
26
|
+
if (errorCode(error) === "EPERM") {
|
|
27
|
+
if (target < 0) continue;
|
|
28
|
+
return "denied";
|
|
29
|
+
}
|
|
27
30
|
if (errorCode(error) !== "ESRCH") throw error;
|
|
28
31
|
}
|
|
29
32
|
}
|
|
@@ -196,13 +196,25 @@ function powershellLiteral(value: string): string {
|
|
|
196
196
|
* as `-e` followed by the first word of the script, and node exits on the syntax error. Given one
|
|
197
197
|
* string it passes that string through as the command line, which is what this builds.
|
|
198
198
|
*/
|
|
199
|
-
function windowsCommandLine(args: readonly string[]): string {
|
|
199
|
+
export function windowsCommandLine(args: readonly string[]): string {
|
|
200
200
|
return args.map((value) => {
|
|
201
201
|
if (value !== "" && !/[\s"]/u.test(value)) return value;
|
|
202
202
|
// A quote is escaped by the backslashes before it, so those double; a trailing run doubles too,
|
|
203
203
|
// because the closing quote would otherwise escape itself against them.
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
let escaped = '"';
|
|
205
|
+
let backslashes = 0;
|
|
206
|
+
for (const character of value) {
|
|
207
|
+
if (character === "\\") {
|
|
208
|
+
backslashes++;
|
|
209
|
+
} else if (character === '"') {
|
|
210
|
+
escaped += "\\".repeat(backslashes * 2 + 1) + '"';
|
|
211
|
+
backslashes = 0;
|
|
212
|
+
} else {
|
|
213
|
+
escaped += "\\".repeat(backslashes) + character;
|
|
214
|
+
backslashes = 0;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return escaped + "\\".repeat(backslashes * 2) + '"';
|
|
206
218
|
}).join(" ");
|
|
207
219
|
}
|
|
208
220
|
|
|
@@ -119,7 +119,14 @@ export async function superviseBuilds(options: {
|
|
|
119
119
|
if (options.signal.aborted) break;
|
|
120
120
|
accepting ??= launch();
|
|
121
121
|
const carrier = accepting;
|
|
122
|
-
|
|
122
|
+
try {
|
|
123
|
+
await carrier.ready;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (accepting === carrier) accepting = undefined;
|
|
126
|
+
if (options.signal.aborted) break;
|
|
127
|
+
await finishInterrupted(build, `Build executor startup failed: ${error instanceof Error ? error.message : String(error)}; create a new Build to continue`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
123
130
|
if (carrier.exited) break;
|
|
124
131
|
if (accepting !== carrier) continue;
|
|
125
132
|
await state.execution.start(build);
|
|
@@ -27,8 +27,10 @@ parser is inside a Segment, a valid bare tag such as `<ALICE>` is a Role Cue. Th
|
|
|
27
27
|
not indentation: the compact spelling
|
|
28
28
|
`<opening><ALICE>I speak first.<BOB>I answer.</opening>` has the same semantic value.
|
|
29
29
|
|
|
30
|
-
A Role Cue is optional.
|
|
31
|
-
|
|
30
|
+
A Role Cue is optional. A Segment without Role Cues contains a roleless Turn. If Roles are used,
|
|
31
|
+
the first Cue must precede that Segment's spoken text. Role state resets when the Segment closes;
|
|
32
|
+
a Role can never leak into the following Segment. Role labels may use Unicode letters, marks and
|
|
33
|
+
numbers; capitalization does not distinguish Roles from Segments.
|
|
32
34
|
|
|
33
35
|
An empty Segment such as `<empty></empty>` (or `<empty/>`) is valid. `empty` is an ordinary
|
|
34
36
|
author-chosen name, not a reserved keyword. It retains the Segment identity and both boundary anchors
|
|
@@ -101,7 +103,7 @@ Units; it does not split the Segment, cut the picture or end a Selection.
|
|
|
101
103
|
<script id="story">
|
|
102
104
|
<exchange>
|
|
103
105
|
<HOST> I use it || every day, || since <2012 | twenty twelve>.
|
|
104
|
-
<GUEST> Even @{proof}
|
|
106
|
+
<GUEST> Even @{proof}on holiday?@{/proof}
|
|
105
107
|
<HOST> @{answer!} Especially then.
|
|
106
108
|
</exchange>
|
|
107
109
|
</script>
|
|
@@ -118,13 +120,18 @@ These units support precise timing and highlighting. A Caption Cue can hold a wh
|
|
|
118
120
|
`||` chooses its handoff independently of character counts or visual line wrapping.
|
|
119
121
|
|
|
120
122
|
Annotations do not create speech boundaries. Comments are transparent (`hel<!--note-->lo`
|
|
121
|
-
remains `hello`)
|
|
123
|
+
remains `hello`), including when an attribute follows a comment or zero-width marker:
|
|
124
|
+
`hello<!--note-->{emphasis}` and `hello@{beat!}{emphasis}` still annotate `hello`.
|
|
125
|
+
An authored space before the attribute remains invalid. A postfix attribute or `||` inside a word
|
|
126
|
+
is invalid. Script analyzes a complete
|
|
122
127
|
prose run before binding these constructs. An explicit Dual correspondence and a speaker/Segment
|
|
123
128
|
boundary remain authored structure. Shared Dual groups still expose their internal speech anchors.
|
|
124
129
|
|
|
125
130
|
A Dual display side is literal authored text, including symbols and emoji: `<😀|smile>` and
|
|
126
131
|
`<.|dot>` have explicit speech correspondence and require no invented speech token for the symbol.
|
|
127
132
|
A literal-only display is one display surface within that correspondence.
|
|
133
|
+
Every Dual needs at least one spoken word on its explicit or shared speech side. `<API|...>` cannot
|
|
134
|
+
provide timed correspondence for `API` and is rejected; punctuation and markers are not spoken words.
|
|
128
135
|
|
|
129
136
|
## Display spelling and separators
|
|
130
137
|
|
|
@@ -3,38 +3,25 @@ import { canonicalStringify } from "@hypit/protocol";
|
|
|
3
3
|
import { ScriptSyntaxError } from "./error.js";
|
|
4
4
|
import { narrativeValue } from "./narrative.js";
|
|
5
5
|
import { parseScript } from "./parser.js";
|
|
6
|
-
|
|
7
|
-
const ROLE = /^[\p{L}\p{M}\p{N}_](?:[\p{L}\p{M}\p{N}_. -]{0,30}[\p{L}\p{M}\p{N}_.-])?$/u;
|
|
6
|
+
import type { ParsedSegment } from "./types.js";
|
|
8
7
|
|
|
9
8
|
function compact(value: string): string {
|
|
10
9
|
return value.replace(/\s+/gu, " ").trim();
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
function formatSegment(
|
|
12
|
+
function formatSegment(source: string, segment: ParsedSegment): string[] {
|
|
13
|
+
const { id, selfClosing, contentRange, atoms } = segment;
|
|
14
14
|
if (selfClosing) return [`<${id}/>`];
|
|
15
|
-
const openEnd = raw.indexOf(">") + 1;
|
|
16
|
-
const closeStart = raw.lastIndexOf(`</${id}>`);
|
|
17
|
-
const body = raw.slice(openEnd, closeStart);
|
|
18
15
|
const lines: string[] = [`<${id}>`];
|
|
19
16
|
const turns: string[] = [];
|
|
20
|
-
let chunkStart =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
const end = body.indexOf(">", cursor + 1);
|
|
28
|
-
if (end < 0) break;
|
|
29
|
-
const inside = body.slice(cursor + 1, end);
|
|
30
|
-
if (!inside.includes("|") && ROLE.test(inside)) {
|
|
31
|
-
const before = compact(body.slice(chunkStart, cursor));
|
|
32
|
-
if (before) turns.push(before);
|
|
33
|
-
chunkStart = cursor;
|
|
34
|
-
}
|
|
35
|
-
cursor = end + 1;
|
|
17
|
+
let chunkStart = contentRange.start;
|
|
18
|
+
for (const atom of atoms) {
|
|
19
|
+
if (atom.kind !== "role") continue;
|
|
20
|
+
const before = compact(source.slice(chunkStart, atom.range.start));
|
|
21
|
+
if (before) turns.push(before);
|
|
22
|
+
chunkStart = atom.range.start;
|
|
36
23
|
}
|
|
37
|
-
const tail = compact(
|
|
24
|
+
const tail = compact(source.slice(chunkStart, contentRange.end));
|
|
38
25
|
if (tail) turns.push(tail);
|
|
39
26
|
lines.push(...turns.map((turn) => ` ${turn}`));
|
|
40
27
|
lines.push(`</${id}>`);
|
|
@@ -64,7 +51,7 @@ export function formatScript(sourceName: string, source: string): string {
|
|
|
64
51
|
} else if (output.length) {
|
|
65
52
|
output.push("");
|
|
66
53
|
}
|
|
67
|
-
output.push(...formatSegment(source
|
|
54
|
+
output.push(...formatSegment(source, segment));
|
|
68
55
|
cursor = end;
|
|
69
56
|
}
|
|
70
57
|
const tail = formatOutside(source.slice(cursor));
|
|
@@ -64,9 +64,9 @@ export const scriptMarkupSurfaces = [
|
|
|
64
64
|
"A Script requires at least one Segment, and natural-language text is refused outside a Segment.",
|
|
65
65
|
"A Segment is opened by its own lower-case name and closed by that exact name, or written self-closing as `<pause/>`; the name is the Segment id, must be unique within the Script, and `script` is reserved. Segments do not nest.",
|
|
66
66
|
"A Role Cue such as `<HOST>` is a bare tag inside a Segment with no close; its turn runs until the next Cue or the end of the Segment, and a Cue may not follow unowned speech in the same Segment. Role state resets when the Segment closes.",
|
|
67
|
-
"Dual Text is written `<display | speech>`: the left side reaches Caption and the right side reaches dialogue and speech. `<display|>`
|
|
67
|
+
"Dual Text is written `<display | speech>`: the left side reaches Caption and the right side reaches dialogue and speech. `<display|>` shares the displayed prose with speech and forms the same complete alignment unit; its word times remain individual. `<|speech>` speaks without displaying. The explicit or shared speech must contain a spoken word; markers and punctuation alone supply no correspondence.",
|
|
68
68
|
"Inside Dual Text, semantic markers belong to the source of spoken text: the explicit right side, or the shared left side when speech is omitted. Display attributes remain visual metadata and never enter spoken text.",
|
|
69
|
-
"A flat token attribute follows a complete display token as `{name}` or `{name=value}`; multiple attributes use one comma-separated block. Attributes do not nest, do not carry timing, and never split a Dual Alignment Unit.",
|
|
69
|
+
"A flat token attribute follows a complete display token as `{name}` or `{name=value}`; multiple attributes use one comma-separated block. Zero-width markers and ordinary-prose comments do not interrupt that attachment; prose whitespace does. Attributes do not nest, do not carry timing, and never split a Dual Alignment Unit.",
|
|
70
70
|
"Selection and Moment markers are fully enclosed in `@{...}` with all sigils inside. They are zero-width, share one name namespace, and may not split a speech token. Surrounding prose spaces remain content; do not add spaces to delimit a name:",
|
|
71
71
|
[
|
|
72
72
|
"| Marker | Meaning |",
|
|
@@ -492,10 +492,10 @@ export function parseScript(
|
|
|
492
492
|
if (!shared && raw[index] === "{") fail("SCRIPT_DUAL_SPEECH_ATTRIBUTE", "Display attributes belong to the display side; escape literal braces in speech.", absoluteStart + index);
|
|
493
493
|
if (shared && raw[index] === "{") {
|
|
494
494
|
const before = raw.slice(partStart, index);
|
|
495
|
-
|
|
495
|
+
addLiteral(before, absoluteStart + partStart);
|
|
496
|
+
if (!runText || /\s$/u.test(runText)) {
|
|
496
497
|
fail("SCRIPT_ATTRIBUTE_TARGET", "A token attribute must immediately follow a display token.", absoluteStart + index);
|
|
497
498
|
}
|
|
498
|
-
addLiteral(before, absoluteStart + partStart);
|
|
499
499
|
const block = parseAttributeBlock(raw.slice(index), absoluteStart + index);
|
|
500
500
|
runAttributes.push({ position: runText.length, offset: absoluteStart + index,
|
|
501
501
|
end: absoluteStart + index + block.length, attributes: block.attributes });
|
|
@@ -517,20 +517,20 @@ export function parseScript(
|
|
|
517
517
|
addLiteral(raw.slice(partStart), absoluteStart + partStart);
|
|
518
518
|
const display = caption ?? spokenParts.join("");
|
|
519
519
|
const sharedMarks = finishLexicalRun();
|
|
520
|
-
if (
|
|
521
|
-
fail("SCRIPT_DUAL_EMPTY",
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
addCaptionRegion(
|
|
525
|
-
display,
|
|
526
|
-
current!.id,
|
|
527
|
-
startToken,
|
|
528
|
-
tokens.length,
|
|
529
|
-
cleanProjection(display) ? "alias" : "hidden",
|
|
530
|
-
{ start: sourceOffset + absoluteStart, end: sourceOffset + absoluteStart + raw.length },
|
|
531
|
-
shared ? sharedMarks : marks,
|
|
532
|
-
);
|
|
520
|
+
if (tokens.length === startToken) {
|
|
521
|
+
fail("SCRIPT_DUAL_EMPTY", shared
|
|
522
|
+
? "Dual Text with omitted speech must contain spoken text on its display side."
|
|
523
|
+
: "Dual Text speech side must contain a spoken word, not only markers or punctuation.", absoluteStart);
|
|
533
524
|
}
|
|
525
|
+
addCaptionRegion(
|
|
526
|
+
display,
|
|
527
|
+
current!.id,
|
|
528
|
+
startToken,
|
|
529
|
+
tokens.length,
|
|
530
|
+
cleanProjection(display) ? "alias" : "hidden",
|
|
531
|
+
{ start: sourceOffset + absoluteStart, end: sourceOffset + absoluteStart + raw.length },
|
|
532
|
+
shared ? sharedMarks : marks,
|
|
533
|
+
);
|
|
534
534
|
};
|
|
535
535
|
|
|
536
536
|
const closeCurrent = (end: number, selfClosing: boolean, contentEnd = end): void => {
|
|
@@ -626,18 +626,13 @@ export function parseScript(
|
|
|
626
626
|
const speech = inside.slice(pipe + 1);
|
|
627
627
|
if (!speech.trim()) {
|
|
628
628
|
consumeSpeechSide(inside.slice(0, pipe), offset + 1, undefined);
|
|
629
|
-
finishLexicalRun();
|
|
630
629
|
offset = end + 1;
|
|
631
630
|
continue;
|
|
632
631
|
}
|
|
633
632
|
assertDualDisplayLiteral(inside.slice(0, pipe), offset + 1);
|
|
634
633
|
const markedDisplay = parseMarkedDisplay(inside.slice(0, pipe), offset + 1);
|
|
635
634
|
const display = markedDisplay.display;
|
|
636
|
-
if (!speech.replace(/@\{[^{}]*\}/gu, "").trim()) {
|
|
637
|
-
fail("SCRIPT_DUAL_EMPTY", "Dual Text speech side must not be empty.", offset);
|
|
638
|
-
}
|
|
639
635
|
consumeSpeechSide(speech, offset + pipe + 2, display, markedDisplay.marks);
|
|
640
|
-
finishLexicalRun();
|
|
641
636
|
offset = end + 1;
|
|
642
637
|
continue;
|
|
643
638
|
}
|
|
@@ -674,7 +669,7 @@ export function parseScript(
|
|
|
674
669
|
for (const piece of literalPieces(raw, textStart)) addText(piece.value, piece.value, piece.start, piece.end, true, piece.positions);
|
|
675
670
|
if (source[offset] === "{") {
|
|
676
671
|
const block = parseAttributeBlock(source.slice(offset), offset);
|
|
677
|
-
if (!
|
|
672
|
+
if (!runText || /\s$/u.test(runText)) {
|
|
678
673
|
fail("SCRIPT_ATTRIBUTE_TARGET", "A token attribute must immediately follow a display token.", offset);
|
|
679
674
|
}
|
|
680
675
|
runAttributes.push({ position: runText.length, offset, end: offset + block.length, attributes: block.attributes });
|