@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
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# @hypit/provider-hyperframes-local
|
|
2
2
|
|
|
3
|
-
Trusted local Provider for
|
|
3
|
+
Trusted local Provider for `@hypit/render-hyperframes@1#render-visual` and `#render-frames`. It stages the
|
|
4
4
|
Resource dependencies declared by a `HyperframesDocument`, renders a silent MP4 with the
|
|
5
5
|
HyperFrames engine, probes the bytes, and returns a verified `RenderedVisual`. Before capturing a typed
|
|
6
6
|
Surface it decodes the exact bytes and checks declared dimensions, still/frame timing, SDR/sRGB and
|
|
7
7
|
opaque/straight-alpha facts. These checks validate the typed rendering input; they do not create
|
|
8
8
|
content identity or hidden output metadata.
|
|
9
9
|
|
|
10
|
+
`render-frames` uses the same staging, source-frame mapping, readiness and opaque PNG capture as
|
|
11
|
+
video export, returning the PNGs before encoding. It accepts a compiled document or a materialized
|
|
12
|
+
HTML project. For selected frames it merges only their required source-frame windows; continuous
|
|
13
|
+
windows decode sequentially. A complete batch shares one staged project and browser lifetime.
|
|
14
|
+
The existing browser selection, explicit preparation, worker settings and cancellation apply.
|
|
15
|
+
`maxRenderedBytes` bounds the total returned PNG bytes, or the encoded MP4 for a video request.
|
|
16
|
+
`render-frames` has its own capability binding; it does not inherit a `render-visual` binding.
|
|
17
|
+
For immediate CLI invocation, the host calls the handler directly: Build admission reservations do
|
|
18
|
+
not coordinate separate CLI processes. Worker limits still bound browsers inside each invocation.
|
|
19
|
+
|
|
10
20
|
Normalized transparent videos displayed by Media or project components use the ordinary video path.
|
|
11
21
|
The engine decodes them to PNG frames with alpha, then Chrome blends them with lower layers and
|
|
12
22
|
the authored Canvas background. Final frames use fast PNG encoding after Chrome has composited
|
|
@@ -73,8 +83,7 @@ an opaque session and chooses PNG separately at capture. It does not patch engin
|
|
|
73
83
|
The Runtime Adapter declares one managed browser program. Prepare it explicitly:
|
|
74
84
|
|
|
75
85
|
```sh
|
|
76
|
-
hypit programs
|
|
77
|
-
hypit doctor --runtime ./hypit.runtime.json
|
|
86
|
+
hypit programs prepare --runtime ./hypit.runtime.json --endpoint hyperframes.local
|
|
78
87
|
```
|
|
79
88
|
|
|
80
89
|
Use the instance name from the Profile. `runtime up` also prepares it and starts the Runtime Worker.
|
|
@@ -110,17 +119,20 @@ receipt is stored. To install into an empty location, explicitly choose `browser
|
|
|
110
119
|
|
|
111
120
|
`config.chromePath` selects a user-managed Chrome/Chromium executable. It cannot be combined with
|
|
112
121
|
`browserVersion` or `browserDownloadBaseUrl`; invalid combinations fail instead of assigning precedence. Relative paths resolve
|
|
113
|
-
from the Runtime
|
|
122
|
+
from the Runtime data root, as does `browserCacheDirectory`. The Profile's `dataRoot` itself resolves
|
|
123
|
+
relative to the Profile file. This mode never downloads or repairs a browser. Its version remains
|
|
114
124
|
under the user's control, including system-browser auto-updates. `HYPERFRAMES_BROWSER_PATH` and
|
|
115
125
|
`PRODUCER_HEADLESS_SHELL_PATH` do not select browsers in this Provider; configure `chromePath`.
|
|
116
126
|
On platforms without a supported managed download, explicitly select an installed browser.
|
|
117
127
|
|
|
118
128
|
`doctor` displays the selected path and its source, and only inspects it. Build preflight, rendering
|
|
119
|
-
and previews never install a browser. `programs up`
|
|
129
|
+
and previews never install a browser. `programs prepare`, `programs up` and `runtime up` display the selected managed
|
|
120
130
|
version, installation location and download URL before running preparation. The probe runs `--version` and checks
|
|
121
131
|
FFmpeg/FFprobe; it does not promise GPU or page compatibility. Capture receives that same selected
|
|
122
|
-
path as the engine's `chromePath`, including its GPU probe.
|
|
123
|
-
|
|
132
|
+
path as the engine's `chromePath`, including its GPU probe. New Builds read current Endpoint
|
|
133
|
+
configuration and project implementation; active Builds keep their selected configuration. A
|
|
134
|
+
Distribution update or change to the Worker's inherited environment requires an explicit Worker
|
|
135
|
+
restart when active work permits. Ordinary Profile edits do not require restarting every process.
|
|
124
136
|
|
|
125
137
|
The Provider's `hypit.dependencyInstallEnv` disables Puppeteer's browser download while preparing its
|
|
126
138
|
engine/producer npm dependencies. The repository `.puppeteerrc.cjs` does the same for checkout installs.
|
|
@@ -11,7 +11,8 @@ import { CaptureConcurrency } from "./concurrency.js";
|
|
|
11
11
|
import { createOpaqueFrameCapture } from "./opaque-capture.js";
|
|
12
12
|
|
|
13
13
|
export type CaptureInput = {
|
|
14
|
-
readonly document: HyperframesDocument
|
|
14
|
+
readonly document: Pick<HyperframesDocument, "frameRate" | "frameCount" | "canvas">;
|
|
15
|
+
readonly frames?: readonly number[];
|
|
15
16
|
readonly range: MediaFrameRange;
|
|
16
17
|
readonly config: ReturnType<typeof resolveExecutionOptions> & { readonly chromePath: string };
|
|
17
18
|
readonly directory: string;
|
|
@@ -24,14 +25,16 @@ export async function captureStagedVisual(input: CaptureInput, controller: Abort
|
|
|
24
25
|
onProgress: (event: HyperframesRenderProgress) => void): Promise<string> {
|
|
25
26
|
const { document, range, config, directory: work } = input;
|
|
26
27
|
const signal = controller.signal;
|
|
27
|
-
const frameCount = range.endFrameExclusive - range.startFrame;
|
|
28
|
+
const frameCount = input.frames?.length ?? range.endFrameExclusive - range.startFrame;
|
|
29
|
+
const selected = input.frames === undefined ? undefined : new Set(input.frames);
|
|
30
|
+
const at = (index: number) => input.frames?.[index] ?? range.startFrame + index;
|
|
28
31
|
const fps = { num: document.frameRate.numerator, den: document.frameRate.denominator };
|
|
29
32
|
const limit = renderWorkerLimit(config, frameCount, fps.num / fps.den);
|
|
30
33
|
const concurrency = new CaptureConcurrency(limit, config.workers === "auto");
|
|
31
34
|
// Short contiguous batches retain sequential capture while allowing free browsers
|
|
32
35
|
// to help with expensive passages. This queue exists only inside this render.
|
|
33
36
|
const batchCount = Math.max(limit, Math.ceil(frameCount / Math.max(1, Math.round(fps.num / fps.den))));
|
|
34
|
-
const batches = distributeFrameRange(
|
|
37
|
+
const batches = distributeFrameRange({ startFrame: 0, endFrameExclusive: frameCount }, batchCount);
|
|
35
38
|
let nextBatch = 0;
|
|
36
39
|
const started = performance.now();
|
|
37
40
|
const elapsedMs = () => Math.round(performance.now() - started);
|
|
@@ -81,7 +84,8 @@ export async function captureStagedVisual(input: CaptureInput, controller: Abort
|
|
|
81
84
|
const sources = new Map<string, { frames: Map<number, string>; width: number; height: number }>();
|
|
82
85
|
let sourceFrames = 0;
|
|
83
86
|
// One source at a time bounds decoder pressure independently of browser concurrency.
|
|
84
|
-
const windows = sourceWindows(slots, range
|
|
87
|
+
const windows = sourceWindows(slots, input.frames === undefined ? [range]
|
|
88
|
+
: input.frames.map(frame => ({ startFrame: frame, endFrameExclusive: frame + 1 })));
|
|
85
89
|
const sourceTotal = windows.reduce((sum, source) => sum + source.windows.reduce((count, window) =>
|
|
86
90
|
count + window.endFrameExclusive - window.startFrame, 0), 0);
|
|
87
91
|
if (sourceTotal > 0) onProgress({ phase: "decoding", completed: 0, total: sourceTotal, elapsedMs: elapsedMs() });
|
|
@@ -119,7 +123,7 @@ export async function captureStagedVisual(input: CaptureInput, controller: Abort
|
|
|
119
123
|
const frameIndex = sourceFrameAt(slot, frame);
|
|
120
124
|
const framePath = sources.get(slot.src)?.frames.get(frameIndex);
|
|
121
125
|
// Browser initialization may seek outside the requested interval.
|
|
122
|
-
if (frame < range.startFrame || frame >= range.endFrameExclusive) continue;
|
|
126
|
+
if (selected === undefined ? frame < range.startFrame || frame >= range.endFrameExclusive : !selected.has(frame)) continue;
|
|
123
127
|
assert(framePath !== undefined, `HyperFrames has no decoded frame ${frameIndex} for ${slot.id}`);
|
|
124
128
|
payloads.set(slot.id, { framePath, frameIndex });
|
|
125
129
|
}
|
|
@@ -132,7 +136,7 @@ export async function captureStagedVisual(input: CaptureInput, controller: Abort
|
|
|
132
136
|
await mkdir(outputFrames);
|
|
133
137
|
onProgress?.({ phase: "prepared", workers: concurrency.target, sourceFrames, elapsedMs: elapsedMs() });
|
|
134
138
|
const jobs: Promise<void>[] = [];
|
|
135
|
-
let active = 0, open = 0, initializing = 0, capturedFrames = 0;
|
|
139
|
+
let active = 0, open = 0, initializing = 0, capturedFrames = 0, capturedBytes = 0;
|
|
136
140
|
let startupMs = 0;
|
|
137
141
|
const launch = (): void => {
|
|
138
142
|
const worker = jobs.length;
|
|
@@ -187,13 +191,18 @@ export async function captureStagedVisual(input: CaptureInput, controller: Abort
|
|
|
187
191
|
if (active > concurrency.target) break;
|
|
188
192
|
const batch = batches[nextBatch++];
|
|
189
193
|
if (batch === undefined) break;
|
|
190
|
-
for (let
|
|
194
|
+
for (let index = batch.startFrame; index < batch.endFrameExclusive; index++) {
|
|
195
|
+
const frame = at(index);
|
|
191
196
|
signal.throwIfAborted();
|
|
192
197
|
const captured = await stage(`worker ${worker} frame ${frame}`, config.frameTimeoutMs, () => captureFrame(frame));
|
|
198
|
+
if (input.frames !== undefined) {
|
|
199
|
+
capturedBytes += captured.buffer.byteLength;
|
|
200
|
+
assert(capturedBytes <= config.maxRenderedBytes, "HyperFrames PNG output exceeds its byte limit");
|
|
201
|
+
}
|
|
193
202
|
timing.seekMs += captured.seekMs;
|
|
194
203
|
timing.prepareMs += captured.prepareMs;
|
|
195
204
|
timing.screenshotMs += captured.screenshotMs;
|
|
196
|
-
await writeFile(join(outputFrames, `${String(
|
|
205
|
+
await writeFile(join(outputFrames, `${String(index).padStart(9, "0")}.png`), captured.buffer);
|
|
197
206
|
completed++;
|
|
198
207
|
capturedFrames++;
|
|
199
208
|
if (performance.now() - lastProgressAt >= 1_000) {
|
|
@@ -239,6 +248,7 @@ export async function captureStagedVisual(input: CaptureInput, controller: Abort
|
|
|
239
248
|
if (failure !== undefined) throw failure;
|
|
240
249
|
signal.throwIfAborted();
|
|
241
250
|
await Promise.all(closing.values());
|
|
251
|
+
if (input.frames !== undefined) return outputFrames;
|
|
242
252
|
const output = join(work, "visual.mp4");
|
|
243
253
|
onProgress({ phase: "encoding", elapsedMs: elapsedMs() });
|
|
244
254
|
const crf = { draft: 28, standard: 23, high: 18 }[config.quality];
|
|
@@ -6,5 +6,5 @@ export type {
|
|
|
6
6
|
HyperframesWorkers,
|
|
7
7
|
} from "./provider.js";
|
|
8
8
|
|
|
9
|
-
export { renderHyperframesVisual } from "./render.js";
|
|
9
|
+
export { renderHyperframesVisual, renderHyperframesFrames } from "./render.js";
|
|
10
10
|
export type { RenderHyperframesVisualOptions, HyperframesRenderProgress } from "./render.js";
|
|
@@ -38,7 +38,7 @@ function outputFrameCount(stream: ProbeStream): number {
|
|
|
38
38
|
|
|
39
39
|
export async function verifyOutput(args: {
|
|
40
40
|
readonly path: string;
|
|
41
|
-
readonly document: HyperframesDocument
|
|
41
|
+
readonly document: Pick<HyperframesDocument, "frameRate" | "frameCount" | "canvas">;
|
|
42
42
|
readonly ffprobePath: string;
|
|
43
43
|
readonly timeoutMs: number;
|
|
44
44
|
readonly maxOutputBytes: number;
|
|
@@ -33,7 +33,10 @@ export async function killRenderTree(pid: number): Promise<void> {
|
|
|
33
33
|
for (const child of descendants.reverse()) {
|
|
34
34
|
for (const target of [-child, child]) {
|
|
35
35
|
try { process.kill(target, "SIGKILL"); }
|
|
36
|
-
catch (error) {
|
|
36
|
+
catch (error) {
|
|
37
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
38
|
+
if (code !== "ESRCH" && !(target < 0 && code === "EPERM")) throw error;
|
|
39
|
+
}
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
const deadline = Date.now() + cleanupMs;
|
|
@@ -76,7 +79,10 @@ export function killRenderDescendantsSync(pid: number): void {
|
|
|
76
79
|
for (const child of descendants.slice(1).reverse()) {
|
|
77
80
|
for (const target of [-child, child]) {
|
|
78
81
|
try { process.kill(target, "SIGKILL"); }
|
|
79
|
-
catch (error) {
|
|
82
|
+
catch (error) {
|
|
83
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
84
|
+
if (code !== "ESRCH" && !(target < 0 && code === "EPERM")) throw error;
|
|
85
|
+
}
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { defineEndpointPackage } from "@hypit/endpoint-kit";
|
|
2
2
|
import { mediaTypes } from "@hypit/media";
|
|
3
|
-
import { renderHyperframesCapabilities, verifyHyperframesVisualRequest } from "@hypit/render-hyperframes";
|
|
3
|
+
import { renderHyperframesCapabilities, renderHyperframesTypes, verifyHyperframesFramesRequest, hyperframesFramesDomain, verifyHyperframesVisualRequest } from "@hypit/render-hyperframes";
|
|
4
4
|
import { canonicalize } from "@hypit/protocol";
|
|
5
|
-
import { renderHyperframesVisual, resolveExecutionOptions, renderWorkerLimit } from "./render.js";
|
|
5
|
+
import { renderHyperframesVisual, renderHyperframesFrames, resolveExecutionOptions, renderWorkerLimit } from "./render.js";
|
|
6
6
|
import type { HyperframesExecutionOptions } from "./options.js";
|
|
7
7
|
import { renderProgressReporter } from "./progress.js";
|
|
8
8
|
|
|
@@ -67,6 +67,33 @@ export function createLocalHyperframesProvider(config: CreateLocalHyperframesPro
|
|
|
67
67
|
await progress.flush();
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
+
}, {
|
|
71
|
+
lifecycle: "immediate" as const,
|
|
72
|
+
capability: renderHyperframesCapabilities.renderFrames,
|
|
73
|
+
returns: renderHyperframesTypes.frames,
|
|
74
|
+
...(config.browserCapacity === undefined ? {} : {
|
|
75
|
+
resources: [{ id: browsers, limit: config.browserCapacity }],
|
|
76
|
+
unitsForRequest: (request: import("@hypit/endpoint-kit").EndpointRequest) => {
|
|
77
|
+
verifyHyperframesFramesRequest(request.constraints);
|
|
78
|
+
const document = hyperframesFramesDomain(request.constraints);
|
|
79
|
+
return { [browsers]: renderWorkerLimit(reserved, request.constraints.frames.length,
|
|
80
|
+
document.frameRate.numerator / document.frameRate.denominator) };
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
handler: async context => {
|
|
84
|
+
const request = context.need.constraints;
|
|
85
|
+
verifyHyperframesFramesRequest(request);
|
|
86
|
+
const progress = renderProgressReporter(context.reportProgress, request.frames.length);
|
|
87
|
+
try {
|
|
88
|
+
const frames = await renderHyperframesFrames(request, { ...config,
|
|
89
|
+
...(execution.chromePath === undefined ? { browserVersion: execution.browserVersion! } : { chromePath: execution.chromePath }),
|
|
90
|
+
browserCacheDirectory: execution.browserCacheDirectory,
|
|
91
|
+
workers: execution.workers, maxWorkers,
|
|
92
|
+
resources: context.resources, onProgress: progress.onProgress,
|
|
93
|
+
...(context.reportDiagnostic === undefined ? {} : { onDiagnostic: context.reportDiagnostic }) });
|
|
94
|
+
return { value: { kind: "inline", value: canonicalize(frames) } };
|
|
95
|
+
} finally { await progress.flush(); }
|
|
96
|
+
},
|
|
70
97
|
}],
|
|
71
98
|
});
|
|
72
99
|
}
|
|
@@ -3,12 +3,12 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import type { EndpointInvocationContext } from "@hypit/endpoint-kit";
|
|
6
|
-
import { stageHyperframesProject } from "@hypit/hyperframes/project";
|
|
6
|
+
import { stageHyperframesProject, stageHyperframesHtmlProject } from "@hypit/hyperframes/project";
|
|
7
7
|
import { sealRenderedVisual } from "@hypit/media";
|
|
8
8
|
import type { MediaFrameRange, RenderedVisual } from "@hypit/media";
|
|
9
9
|
import { verifyCompositableSurfaceFile } from "@hypit/media-execution";
|
|
10
|
-
import { verifyHyperframesVisualRequest } from "@hypit/render-hyperframes";
|
|
11
|
-
import type { HyperframesVisualRequest } from "@hypit/render-hyperframes";
|
|
10
|
+
import { verifyHyperframesVisualRequest, verifyHyperframesFramesRequest, hyperframesFramesDomain } from "@hypit/render-hyperframes";
|
|
11
|
+
import type { HyperframesVisualRequest, HyperframesFramesRequest, HyperframesFrames } from "@hypit/render-hyperframes";
|
|
12
12
|
import { isStreamingResourceStore } from "@hypit/runtime";
|
|
13
13
|
import type { HyperframesExecutionOptions } from "./options.js";
|
|
14
14
|
import { assert, positiveInteger } from "./process.js";
|
|
@@ -75,10 +75,22 @@ export async function renderHyperframesVisual(
|
|
|
75
75
|
options: RenderHyperframesVisualOptions,
|
|
76
76
|
): Promise<RenderedVisual> {
|
|
77
77
|
verifyHyperframesVisualRequest(request);
|
|
78
|
+
return capture(request, options) as Promise<RenderedVisual>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function renderHyperframesFrames(request: HyperframesFramesRequest, options: RenderHyperframesVisualOptions): Promise<HyperframesFrames> {
|
|
82
|
+
verifyHyperframesFramesRequest(request);
|
|
83
|
+
return capture(request, options) as Promise<HyperframesFrames>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function capture(request: HyperframesVisualRequest | HyperframesFramesRequest, options: RenderHyperframesVisualOptions): Promise<RenderedVisual | HyperframesFrames> {
|
|
78
87
|
const config = { ...resolveExecutionOptions(options), chromePath: browserExecutablePath(options) };
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
88
|
+
const frames = "frames" in request ? request.frames : undefined;
|
|
89
|
+
const document = "frames" in request ? hyperframesFramesDomain(request) : request.document;
|
|
90
|
+
const range = frames === undefined
|
|
91
|
+
? (request as HyperframesVisualRequest).range ?? { startFrame: 0, endFrameExclusive: document.frameCount }
|
|
92
|
+
: { startFrame: frames[0]!, endFrameExclusive: frames.at(-1)! + 1 };
|
|
93
|
+
const frameCount = frames?.length ?? range.endFrameExclusive - range.startFrame;
|
|
82
94
|
const controller = new AbortController();
|
|
83
95
|
const signal = options.signal === undefined ? controller.signal : AbortSignal.any([controller.signal, options.signal]);
|
|
84
96
|
const started = performance.now();
|
|
@@ -103,23 +115,25 @@ export async function renderHyperframesVisual(
|
|
|
103
115
|
signal.throwIfAborted();
|
|
104
116
|
await requireBrowserExecutable(config.chromePath, config.browserVersion);
|
|
105
117
|
await options.onDiagnostic?.({ level: "info", message:
|
|
106
|
-
`
|
|
118
|
+
`Capture ${frameCount} frames; workers ${config.workers} (limit ${renderWorkerLimit(config, frameCount, document.frameRate.numerator / document.frameRate.denominator)}); opaque fast PNG; GPU ${config.browserGpu}; browser ${config.chromePath}`
|
|
119
|
+
+ (frames === undefined ? `; quality ${config.quality}; encoder ${config.ffmpegPath}` : "; PNG output") });
|
|
107
120
|
options.onProgress?.({ phase: "staging", elapsedMs: 0 });
|
|
108
121
|
work = await mkdtemp(join(tmpdir(), "hypit-hyperframes-local-"));
|
|
109
|
-
|
|
110
|
-
read: async (artifact, readSignal) => {
|
|
122
|
+
const read: import("@hypit/hyperframes/project").HyperframesArtifactReader = async (artifact, readSignal) => {
|
|
111
123
|
const io = { signal: readSignal! };
|
|
112
124
|
const bytes = isStreamingResourceStore(options.resources)
|
|
113
125
|
? await options.resources.open(artifact.resource, io) : await options.resources.get(artifact.resource, io);
|
|
114
126
|
assert(bytes !== undefined, `HyperFrames Artifact ${artifact.resource} is unavailable`);
|
|
115
127
|
return bytes;
|
|
116
|
-
}
|
|
128
|
+
};
|
|
129
|
+
if ("project" in request) await stageHyperframesHtmlProject({ project: request.project, directory: work, signal, read });
|
|
130
|
+
else await stageHyperframesProject({ document: request.document, directory: work, signal, read,
|
|
117
131
|
validateSurface: (surface, path, probeSignal) => verifyCompositableSurfaceFile({ surface, path,
|
|
118
132
|
ffprobePath: config.ffprobePath, processTimeoutMs: config.processTimeoutMs,
|
|
119
133
|
maxProbeOutputBytes: config.maxProcessOutputBytes, signal: probeSignal! }),
|
|
120
134
|
});
|
|
121
135
|
changePhase("decoding source frames");
|
|
122
|
-
await runCaptureProcess({ document, range, config, directory: work,
|
|
136
|
+
await runCaptureProcess({ document, range, ...(frames === undefined ? {} : { frames }), config, directory: work,
|
|
123
137
|
engineModule: import.meta.resolve("@hyperframes/engine"),
|
|
124
138
|
producerModule: import.meta.resolve("@hyperframes/producer"),
|
|
125
139
|
}, signal, (event) => {
|
|
@@ -132,22 +146,33 @@ export async function renderHyperframesVisual(
|
|
|
132
146
|
signal.throwIfAborted();
|
|
133
147
|
changePhase("storing output");
|
|
134
148
|
options.onProgress?.({ phase: "storing", elapsedMs: Math.round(performance.now() - started) });
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
const store = async (output: string, mediaType: string) => {
|
|
150
|
+
if (isStreamingResourceStore(options.resources)) {
|
|
151
|
+
const stream = createReadStream(output, { signal });
|
|
152
|
+
const closed = finished(stream).catch(() => {});
|
|
153
|
+
try { return await options.resources.putStream(stream, mediaType, { signal }); }
|
|
154
|
+
finally { stream.destroy(); await closed; }
|
|
155
|
+
}
|
|
156
|
+
return options.resources.put(await readFile(output, { signal }), mediaType, { signal });
|
|
157
|
+
};
|
|
158
|
+
let result: RenderedVisual | HyperframesFrames;
|
|
159
|
+
if (frames === undefined) {
|
|
160
|
+
result = sealRenderedVisual({ frameRate: document.frameRate, frameCount, canvas: document.canvas,
|
|
161
|
+
artifact: await store(join(work, "visual.mp4"), "video/mp4") });
|
|
142
162
|
} else {
|
|
143
|
-
|
|
163
|
+
const artifacts = [];
|
|
164
|
+
for (let index = 0; index < frames.length; index++) {
|
|
165
|
+
signal.throwIfAborted();
|
|
166
|
+
artifacts.push(await store(join(work, "frames", `${String(index).padStart(9, "0")}.png`), "image/png"));
|
|
167
|
+
}
|
|
168
|
+
result = artifacts;
|
|
144
169
|
}
|
|
145
170
|
signal.throwIfAborted();
|
|
146
171
|
|
|
147
172
|
changePhase("complete");
|
|
148
173
|
await Promise.all(diagnostics);
|
|
149
174
|
options.onProgress?.({ phase: "complete", frames: frameCount, elapsedMs: Math.round(performance.now() - started) });
|
|
150
|
-
return
|
|
175
|
+
return result;
|
|
151
176
|
} catch (error) {
|
|
152
177
|
if (signal.aborted) throw signal.reason;
|
|
153
178
|
controller.abort(error);
|
|
@@ -51,9 +51,10 @@ export function sourceFrameAt(slot: VideoSlot, frame: number): number {
|
|
|
51
51
|
return value;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
export function sourceWindows(slots: readonly VideoSlot[],
|
|
54
|
+
export function sourceWindows(slots: readonly VideoSlot[], selection: MediaFrameRange | readonly MediaFrameRange[]) {
|
|
55
|
+
const ranges: readonly MediaFrameRange[] = Array.isArray(selection) ? selection : [selection as MediaFrameRange];
|
|
55
56
|
const sources = new Map<string, { fps: VideoSlot["sourceFps"]; windows: MediaFrameRange[] }>();
|
|
56
|
-
for (const slot of slots) {
|
|
57
|
+
for (const range of ranges) for (const slot of slots) {
|
|
57
58
|
const first = Math.max(range.startFrame, slot.startFrame);
|
|
58
59
|
const last = Math.min(range.endFrameExclusive, slot.endFrameExclusive) - 1;
|
|
59
60
|
if (last < first) continue;
|
|
@@ -123,12 +123,11 @@ size and part concurrency, retries a failed part with a fresh signed URL, comple
|
|
|
123
123
|
one upload, and then passes the returned HTTPS URL to generation or transcription. Signing requests
|
|
124
124
|
contain at most the service's 128-part limit; all batches belong to the same upload. One
|
|
125
125
|
Resource identity with the same declared person-reference classification is uploaded once within one Runtime operation. Hypit keeps no upload catalog or
|
|
126
|
-
cross-Build cache. Seedance visual references
|
|
126
|
+
cross-Build cache. Seedance visual references require boolean `personReference` in their media fields;
|
|
127
127
|
the mapping declares it as a resource-transport field and the upload session receives
|
|
128
|
-
`is_person_reference`, preserving true
|
|
128
|
+
`is_person_reference`, preserving true and false. It stays out of the generation body.
|
|
129
129
|
This covers reference images, reference videos, and first/last frames for every declared Seedance
|
|
130
|
-
variant.
|
|
131
|
-
so omission does not enable detection or person-reference preparation.
|
|
130
|
+
variant. Seedance rejects omission before upload; no automatic face detection is requested.
|
|
132
131
|
HypiHub stores the authored classification and prepares the applicable upstream person reference;
|
|
133
132
|
this Provider does not detect faces or select an upstream private-avatar group.
|
|
134
133
|
|
|
@@ -25,7 +25,7 @@ each media input as a typed item with its `role` (`first_frame`, `last_frame`, `
|
|
|
25
25
|
|
|
26
26
|
The Seedance endpoints add `generate_audio`. Monid documents no web search field for them, so
|
|
27
27
|
`web-search="true"` is unsupported, and Seedance 2.5 frame mode (`first-frame` present) requires
|
|
28
|
-
`aspect-ratio="adaptive"`. Seedance visual references
|
|
28
|
+
`aspect-ratio="adaptive"`. Seedance visual references require `person-reference`; the Provider
|
|
29
29
|
accepts the declaration and transmits nothing for it, since the endpoint has no field for it.
|
|
30
30
|
Seedance rejects reference images and videos that contain a real human face; Monid offers no way to
|
|
31
31
|
register authorized portrait material, so such a request fails with the upstream moderation error.
|
|
@@ -19,7 +19,7 @@ the models the Distribution already describes.
|
|
|
19
19
|
Video requests write the prompt and each media input as one item of the protocol's `content`
|
|
20
20
|
array with its `role` (`first_frame`, `last_frame`, `reference_image`, `reference_video`,
|
|
21
21
|
`reference_audio`), then `resolution`, `ratio`, `duration` and, for Seedance, `generate_audio`;
|
|
22
|
-
`web-search="true"` adds `tools: [{ "type": "web_search" }]`. Seedance visual references
|
|
22
|
+
`web-search="true"` adds `tools: [{ "type": "web_search" }]`. Seedance visual references require
|
|
23
23
|
`person-reference`; the Provider accepts the declaration and transmits nothing for it, since the Ark
|
|
24
24
|
protocol has no such field. Seedance 2.0 and 2.5 reject reference images and videos that contain a
|
|
25
25
|
real human face; TokenDance offers no way to register authorized portrait material, so such a request
|
|
@@ -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);
|