@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
|
@@ -24,6 +24,10 @@ function findClose(input: ScriptSurfaceInput): { readonly start: number; readonl
|
|
|
24
24
|
const close = `</${input.tag}>`;
|
|
25
25
|
let cursor = input.contentStart;
|
|
26
26
|
while (cursor < input.source.length) {
|
|
27
|
+
if (input.source[cursor] === "\\") {
|
|
28
|
+
cursor += 2;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
27
31
|
if (input.source.startsWith("<!--", cursor)) {
|
|
28
32
|
const commentEnd = input.source.indexOf("-->", cursor + 4);
|
|
29
33
|
if (commentEnd < 0) {
|
|
@@ -33,9 +37,7 @@ function findClose(input: ScriptSurfaceInput): { readonly start: number; readonl
|
|
|
33
37
|
continue;
|
|
34
38
|
}
|
|
35
39
|
if (input.source.startsWith(close, cursor)) {
|
|
36
|
-
|
|
37
|
-
for (let before = cursor - 1; before >= 0 && input.source[before] === "\\"; before -= 1) slashes += 1;
|
|
38
|
-
if (slashes % 2 === 0) return { start: cursor, end: cursor + close.length };
|
|
40
|
+
return { start: cursor, end: cursor + close.length };
|
|
39
41
|
}
|
|
40
42
|
cursor += 1;
|
|
41
43
|
}
|
|
@@ -35,7 +35,7 @@ export type ParsedSegment = NarrativeSegment & {
|
|
|
35
35
|
readonly index: number;
|
|
36
36
|
readonly atoms: readonly ParsedAtom[];
|
|
37
37
|
readonly range: SourceRange;
|
|
38
|
-
/** Exact body range between
|
|
38
|
+
/** Exact body range between Segment tags for Script-owned source edits and formatting. */
|
|
39
39
|
readonly contentRange: SourceRange;
|
|
40
40
|
readonly selfClosing: boolean;
|
|
41
41
|
};
|
|
@@ -9,5 +9,5 @@ It recognizes exactly one mandatory bounded Header:
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
The Header selects an exact trusted Frontend. There is no suffix dispatch and no default parser.
|
|
12
|
-
The package masks the Header while preserving
|
|
12
|
+
The package masks the Header while preserving UTF-16 source offsets and line breaks, but does not recognize imports,
|
|
13
13
|
XML, Script, Recipes, Run syntax or domain Types. Those belong to the selected Frontend.
|
|
@@ -75,6 +75,7 @@ export function parseSourceHeader(sourceName: string, text: string): SourceHeade
|
|
|
75
75
|
|
|
76
76
|
/** Preserve every original offset while making the Header ordinary whitespace to body Frontends. */
|
|
77
77
|
export function maskSourceHeader(text: string, header: SourceHeader): string {
|
|
78
|
-
|
|
78
|
+
// Source ranges use UTF-16 offsets: replace each code unit, not each code point.
|
|
79
|
+
const prefix = text.slice(0, header.end).replace(/[^\r\n]/g, " ");
|
|
79
80
|
return `${prefix}${text.slice(header.end)}`;
|
|
80
81
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Hypit Studio
|
|
2
2
|
|
|
3
|
+
For direct picture inspection, `hypit snapshot --studio <studio-url>` reads the current compiled
|
|
4
|
+
`HyperframesDocument` from `GET /__studio/document` and its existing `/__studio/material/<resource>`
|
|
5
|
+
resources. `GET /__studio/visual.html` exposes the same materialized picture without the interactive
|
|
6
|
+
Studio playback shim or audio. Both representations come from the same compilation as the displayed
|
|
7
|
+
preview. The snapshot invocation uses the selected Runtime Profile's frame Provider and creates no
|
|
8
|
+
Build. During a pending Source compilation the export route reports that state; a failed update
|
|
9
|
+
reports its error instead of returning the previous picture as current.
|
|
10
|
+
|
|
3
11
|
The single official Web Studio for SVML. It opens an explicit Run Source,
|
|
4
12
|
traces its Film or Render target back to the semantic and visual projections
|
|
5
13
|
Studio can edit, runs deterministic Producers and explicitly permitted transient Needs, and composites the
|
|
@@ -159,6 +167,11 @@ the selected entity. A reference can resolve to a shared Frame or Recipe, so one
|
|
|
159
167
|
may affect several consumers. Structured fields save together when editing ends and the value is complete; missing required values stay in the editor with a completion hint.
|
|
160
168
|
Check save status; source conflicts reject stale edits rather than overwrite newer files.
|
|
161
169
|
|
|
170
|
+
Script source ranges come from its raw Surface and Companion. Marker edits use Script's parsed
|
|
171
|
+
anchors and preserve unrelated prose, whitespace and word attributes; the displayed timeline words
|
|
172
|
+
are not a replacement text source. Recipe parameter reads use the same Source Header preparation
|
|
173
|
+
as compilation, preserving UTF-16 offsets for the exact property being edited.
|
|
174
|
+
|
|
162
175
|
The Timeline owns the editor's complete range; displayed objects do not extend it. Take placement
|
|
163
176
|
and complete extent are reference information in Studio.
|
|
164
177
|
|
|
@@ -3,6 +3,7 @@ import { relative } from "node:path";
|
|
|
3
3
|
import type { Plugin } from "vite";
|
|
4
4
|
import { createFeedbackStore, FeedbackConflict } from "./feedback-store.js";
|
|
5
5
|
import { readFeedbackMutation } from "./feedback.js";
|
|
6
|
+
import { allowsStudioMutation } from "./mutation-origin.js";
|
|
6
7
|
import type { FeedbackDocument, FeedbackView } from "./feedback.js";
|
|
7
8
|
|
|
8
9
|
/** Review storage is separate from compilation, Results and Agent delivery. */
|
|
@@ -23,6 +24,12 @@ export function studioFeedbackPlugin(workspaceRoot: string, runPath: string): Pl
|
|
|
23
24
|
server.httpServer?.once("close", () => watcher.close());
|
|
24
25
|
server.middlewares.use((request, response, next) => {
|
|
25
26
|
if (new URL(request.url ?? "/", "http://studio.hypit.local").pathname !== "/__studio/feedback") return next();
|
|
27
|
+
if (request.method === "POST" && !allowsStudioMutation(request.headers)) {
|
|
28
|
+
response.statusCode = 403;
|
|
29
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
30
|
+
response.end(JSON.stringify({ error: "Studio mutation must come from this local Studio session." }));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
26
33
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
27
34
|
response.setHeader("cache-control", "no-store");
|
|
28
35
|
void (async () => {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders } from "node:http";
|
|
2
|
+
|
|
3
|
+
/** Studio writes belong to the local page serving this Studio session. */
|
|
4
|
+
export function allowsStudioMutation(headers: IncomingHttpHeaders): boolean {
|
|
5
|
+
const host = headers.host;
|
|
6
|
+
if (host === undefined) return false;
|
|
7
|
+
let target: URL;
|
|
8
|
+
try { target = new URL(`http://${host}`); } catch { return false; }
|
|
9
|
+
if (!["localhost", "127.0.0.1", "[::1]"].includes(target.hostname) || target.host !== host.toLowerCase()) return false;
|
|
10
|
+
|
|
11
|
+
const origin = headers.origin;
|
|
12
|
+
if (origin !== undefined) {
|
|
13
|
+
let source: URL;
|
|
14
|
+
try { source = new URL(origin); } catch { return false; }
|
|
15
|
+
if (source.origin !== origin || source.origin !== target.origin) return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const site = headers["sec-fetch-site"];
|
|
19
|
+
return site === undefined || site === "same-origin" || site === "none";
|
|
20
|
+
}
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
} from "@hypit/studio-adapter";
|
|
18
18
|
import { parseSvs } from "@hypit/svs";
|
|
19
19
|
import { parseOpeningTag } from "@hypit/markup";
|
|
20
|
+
import { prepareAuthorSource } from "@hypit/elaborator";
|
|
20
21
|
import { parameterControlForSchema, parameterRecordSchema } from "./parameter-values.js";
|
|
21
22
|
import type { CanonicalValue } from "@hypit/protocol";
|
|
22
23
|
|
|
@@ -132,12 +133,6 @@ function sourceAbsolute(root: string, path: string, base?: string): string {
|
|
|
132
133
|
return resolve(directory, path);
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
function svsText(source: string): string {
|
|
136
|
-
const header = /^\s*<\?svml[\s\S]*?\?>/u.exec(source);
|
|
137
|
-
if (header === null) return source;
|
|
138
|
-
return `${header[0].replace(/[^\r\n]/gu, " ")}${source.slice(header[0].length)}`;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
136
|
function recipeParameters(input: {
|
|
142
137
|
readonly root: string;
|
|
143
138
|
readonly files: readonly StudioSourceFile[];
|
|
@@ -176,7 +171,8 @@ function recipeParameters(input: {
|
|
|
176
171
|
const source = sourceFor(input.root, imported.source, input.files, input.current.path);
|
|
177
172
|
if (source === undefined || source.language !== "svs") return [];
|
|
178
173
|
const recipePath = parts.join(".");
|
|
179
|
-
const
|
|
174
|
+
const prepared = prepareAuthorSource({ id: source.path, name: source.path, text: source.text });
|
|
175
|
+
const parsed = parseSvs(source.path, prepared.text);
|
|
180
176
|
const recipe = parsed.recipes.find((item) => item.value.path === recipePath);
|
|
181
177
|
if (recipe === undefined) return [];
|
|
182
178
|
return input.recipe.bindings.flatMap((declaration): readonly StudioSourceBinding[] => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AudioTrack, Composition } from "@hypit/composition";
|
|
2
2
|
import { compileHyperframesDocument, materializeHyperframesHtml } from "@hypit/hyperframes";
|
|
3
|
+
import type { HyperframesDocument } from "@hypit/hyperframes";
|
|
3
4
|
import type { ProgramSpace } from "@hypit/program-space";
|
|
4
5
|
|
|
5
6
|
import { injectRuntimeShim } from "./runtime-shim.js";
|
|
@@ -19,6 +20,11 @@ export type RenderInput = {
|
|
|
19
20
|
* material all come from the projection selected by the Run.
|
|
20
21
|
*/
|
|
21
22
|
export function renderPreview(input: RenderInput): string {
|
|
23
|
+
return renderStudioProgramme(input).preview;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The same compiled picture serves immediate frame capture and interactive playback. */
|
|
27
|
+
export function renderStudioProgramme(input: RenderInput): { readonly document: HyperframesDocument; readonly html: string; readonly preview: string } {
|
|
22
28
|
const document = compileHyperframesDocument(input.composition, input.space);
|
|
23
29
|
const html = materializeHyperframesHtml(document, (artifact) => {
|
|
24
30
|
// The only Artifacts a preview can reference are files the author already
|
|
@@ -51,5 +57,5 @@ export function renderPreview(input: RenderInput): string {
|
|
|
51
57
|
fadeInSamples: clip.fadeInSamples, fadeOutSamples: clip.fadeOutSamples,
|
|
52
58
|
}))}"></audio>`;
|
|
53
59
|
}).join("");
|
|
54
|
-
return injectRuntimeShim(html, audio);
|
|
60
|
+
return { document, html, preview: injectRuntimeShim(html, audio) };
|
|
55
61
|
}
|
|
@@ -15,6 +15,7 @@ import type { ServedFile } from "./compile.js";
|
|
|
15
15
|
import type { StudioDomain } from "./domain.js";
|
|
16
16
|
import type { StudioCompanionRegistry } from "./studio-registry.js";
|
|
17
17
|
import { loadStudioRun } from "./run.js";
|
|
18
|
+
import { allowsStudioMutation } from "./mutation-origin.js";
|
|
18
19
|
import { parameterAuthorValue, parameterOption, serializeParameterValue, serializeAttributeGroup, validateParameterValue } from "./parameter-values.js";
|
|
19
20
|
import { readStudioSession } from "./session.js";
|
|
20
21
|
import type { Range, StudioFailure, StudioLibraryRequest, StudioLibraryView, StudioMutation, StudioSnapshot } from "./shared.js";
|
|
@@ -80,6 +81,8 @@ class StudioMutationRejected extends Error {}
|
|
|
80
81
|
|
|
81
82
|
export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
82
83
|
let snapshot: StudioSnapshot | undefined;
|
|
84
|
+
let visualHtml: string | undefined;
|
|
85
|
+
let visualDocument: import("@hypit/hyperframes").HyperframesDocument | undefined;
|
|
83
86
|
let failure: StudioFailure | undefined;
|
|
84
87
|
let material: ReadonlyMap<string, ServedFile> = new Map();
|
|
85
88
|
let revision = 0;
|
|
@@ -158,6 +161,8 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
158
161
|
if (attempt !== requestedRevision) return;
|
|
159
162
|
revision = attempt;
|
|
160
163
|
snapshot = result.snapshot;
|
|
164
|
+
visualHtml = result.visualHtml;
|
|
165
|
+
visualDocument = result.document;
|
|
161
166
|
material = result.material;
|
|
162
167
|
failure = undefined;
|
|
163
168
|
if (notify) server?.ws.send({ type: "custom", event: "studio:snapshot", data: snapshot });
|
|
@@ -473,6 +478,10 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
473
478
|
value.middlewares.use((request, response, next) => {
|
|
474
479
|
const url = new URL(request.url ?? "/", "http://studio.hypit.local");
|
|
475
480
|
if (request.method === "PUT" && url.pathname === "/__studio/source") {
|
|
481
|
+
if (!allowsStudioMutation(request.headers)) {
|
|
482
|
+
json(response, 403, { error: "Cross-origin Studio mutations are prohibited." });
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
476
485
|
void (async () => {
|
|
477
486
|
let acquired = false;
|
|
478
487
|
try {
|
|
@@ -524,6 +533,10 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
524
533
|
return;
|
|
525
534
|
}
|
|
526
535
|
if (request.method === "PUT" && url.pathname === "/__studio/artifact-name") {
|
|
536
|
+
if (!allowsStudioMutation(request.headers)) {
|
|
537
|
+
json(response, 403, { error: "Cross-origin Studio mutations are prohibited." });
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
527
540
|
void (async () => {
|
|
528
541
|
try {
|
|
529
542
|
const chunks: Buffer[] = [];
|
|
@@ -546,6 +559,10 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
546
559
|
return;
|
|
547
560
|
}
|
|
548
561
|
if (request.method === "POST" && url.pathname === "/__studio/mutation") {
|
|
562
|
+
if (!allowsStudioMutation(request.headers)) {
|
|
563
|
+
json(response, 403, { error: "Cross-origin Studio mutations are prohibited." });
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
549
566
|
void (async () => {
|
|
550
567
|
try {
|
|
551
568
|
const chunks: Buffer[] = [];
|
|
@@ -580,6 +597,28 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
580
597
|
next();
|
|
581
598
|
return;
|
|
582
599
|
}
|
|
600
|
+
if (url.pathname === "/__studio/visual.html" || url.pathname === "/__studio/document") {
|
|
601
|
+
void (async () => {
|
|
602
|
+
if (timer !== undefined || publishing > 0) {
|
|
603
|
+
json(response, 409, { error: "Studio is compiling a Source change; capture after the updated preview is ready." });
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (snapshot === undefined && failure === undefined) await publish(++requestedRevision);
|
|
607
|
+
if (failure !== undefined || visualHtml === undefined) {
|
|
608
|
+
json(response, 500, failure ?? { error: "Studio has no compiled picture." });
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (url.pathname === "/__studio/document") {
|
|
612
|
+
json(response, 200, visualDocument);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
response.statusCode = 200;
|
|
616
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
617
|
+
response.setHeader("cache-control", "no-store");
|
|
618
|
+
response.end(request.method === "HEAD" ? undefined : visualHtml);
|
|
619
|
+
})();
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
583
622
|
if (url.pathname === "/__studio/session") {
|
|
584
623
|
void (async () => {
|
|
585
624
|
if (snapshot === undefined && failure === undefined) {
|
|
@@ -9,7 +9,7 @@ import type { ServedFile } from "./compile.js";
|
|
|
9
9
|
import type { StudioDomain } from "./domain.js";
|
|
10
10
|
import type { Observations } from "./observe.js";
|
|
11
11
|
import { preview } from "./programme.js";
|
|
12
|
-
import {
|
|
12
|
+
import { renderStudioProgramme } from "./preview/render.js";
|
|
13
13
|
import type { RunPlan } from "./run.js";
|
|
14
14
|
import type { StudioSnapshot } from "./shared.js";
|
|
15
15
|
import type { StudioCompanionRegistry } from "./studio-registry.js";
|
|
@@ -47,6 +47,8 @@ function sourceFiles(run: RunPlan): readonly StudioSourceFile[] {
|
|
|
47
47
|
|
|
48
48
|
export type StudioSession = {
|
|
49
49
|
readonly snapshot: StudioSnapshot;
|
|
50
|
+
readonly document: import("@hypit/hyperframes").HyperframesDocument;
|
|
51
|
+
readonly visualHtml: string;
|
|
50
52
|
readonly material: ReadonlyMap<string, ServedFile>;
|
|
51
53
|
readonly observations: Observations;
|
|
52
54
|
readonly projections: readonly StudioViewRequirement[];
|
|
@@ -77,7 +79,7 @@ export async function readStudioSession(input: {
|
|
|
77
79
|
projections: inspection.projections,
|
|
78
80
|
...(input.transientExecution === undefined ? {} : { transientExecution: input.transientExecution }),
|
|
79
81
|
});
|
|
80
|
-
const rendered =
|
|
82
|
+
const rendered = renderStudioProgramme({
|
|
81
83
|
composition: built.composition,
|
|
82
84
|
space: built.space as never,
|
|
83
85
|
served: new Set(built.served.keys()),
|
|
@@ -85,6 +87,8 @@ export async function readStudioSession(input: {
|
|
|
85
87
|
const text = readFileSync(input.run.authorSource, "utf8");
|
|
86
88
|
const files = sourceFiles(input.run);
|
|
87
89
|
return {
|
|
90
|
+
document: rendered.document,
|
|
91
|
+
visualHtml: rendered.html,
|
|
88
92
|
snapshot: snapshot(input.registry, built, {
|
|
89
93
|
revision: input.revision,
|
|
90
94
|
path: input.sourcePath ?? input.run.authorSource,
|
|
@@ -99,7 +103,7 @@ export async function readStudioSession(input: {
|
|
|
99
103
|
},
|
|
100
104
|
canvas: built.canvas,
|
|
101
105
|
frameRate: built.frameRate,
|
|
102
|
-
preview: { kind: "hyperframes", srcdoc: rendered },
|
|
106
|
+
preview: { kind: "hyperframes", srcdoc: rendered.preview },
|
|
103
107
|
workspaceRoot: input.workspaceRoot,
|
|
104
108
|
sourceFiles: files,
|
|
105
109
|
surfaces: input.domain.surfaces,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A tokenizer for SVML's own surface syntax.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* than through a general highlighter that has no grammar for it. The Script
|
|
4
|
+
* Recognize the common Markup shell and Script prose for highlighting, without
|
|
5
|
+
* validating package-owned Surfaces. The Script
|
|
7
6
|
* markers are the point: `@{claim} … @{/claim}` is what a Media Item binds to, so it
|
|
8
7
|
* has to read as a distinct thing from an ordinary attribute.
|
|
9
8
|
*
|
|
@@ -105,8 +104,7 @@ export function tokenizeSvml(source: string): readonly Token[] {
|
|
|
105
104
|
cursor = nameStart + name[0].length;
|
|
106
105
|
cursor = tokenizeAttributes(source, cursor, push);
|
|
107
106
|
|
|
108
|
-
// Script
|
|
109
|
-
// markers, not markup, and must be scanned by different rules.
|
|
107
|
+
// Script's Raw Surface has its own prose grammar.
|
|
110
108
|
if (!closing && localName(name[0]) === "script" && source[cursor - 2] !== "/") {
|
|
111
109
|
cursor = tokenizeScriptBody(source, cursor, name[0], push);
|
|
112
110
|
}
|
|
@@ -167,6 +165,7 @@ function tokenizeAttributes(source: string, from: number, push: Push): number {
|
|
|
167
165
|
function tokenizeScriptBody(source: string, from: number, tag: string, push: Push): number {
|
|
168
166
|
const close = `</${tag}>`;
|
|
169
167
|
let cursor = from;
|
|
168
|
+
let inSegment = false;
|
|
170
169
|
while (cursor < source.length) {
|
|
171
170
|
if (source[cursor] === "\\") { cursor += 2; continue; }
|
|
172
171
|
if (source.startsWith(close, cursor)) {
|
|
@@ -199,14 +198,16 @@ function tokenizeScriptBody(source: string, from: number, tag: string, push: Pus
|
|
|
199
198
|
}
|
|
200
199
|
const closing = source[cursor + 1] === "/";
|
|
201
200
|
const nameStart = cursor + (closing ? 2 : 1);
|
|
202
|
-
const name = NAME.exec(source.slice(nameStart));
|
|
203
|
-
if (name === null) { cursor += 1; continue; }
|
|
204
201
|
const end = source.indexOf(">", nameStart);
|
|
205
202
|
const stop = end < 0 ? source.length : end + 1;
|
|
203
|
+
const selfClosing = end >= 0 && source[end - 1] === "/";
|
|
204
|
+
const nameEnd = end < 0 ? stop : end - (selfClosing ? 1 : 0);
|
|
206
205
|
push(cursor, nameStart, "punct");
|
|
207
|
-
//
|
|
208
|
-
push(nameStart,
|
|
209
|
-
push(
|
|
206
|
+
// Context distinguishes Segments from Role Cues, regardless of language or case.
|
|
207
|
+
push(nameStart, nameEnd, inSegment && !closing ? "role" : "tag");
|
|
208
|
+
push(nameEnd, stop, "punct");
|
|
209
|
+
if (closing) inSegment = false;
|
|
210
|
+
else if (!inSegment && !selfClosing) inSegment = true;
|
|
210
211
|
cursor = stop;
|
|
211
212
|
continue;
|
|
212
213
|
}
|
|
@@ -164,7 +164,7 @@ write target. The `fixed` case means no temporal gesture writes that endpoint, n
|
|
|
164
164
|
parameter editing is forbidden.
|
|
165
165
|
|
|
166
166
|
- `@hypit/script` owns tokenization, anchor identities, legal marker sites, and structural writeback.
|
|
167
|
-
It
|
|
167
|
+
It relocates markers without rewriting unrelated prose whitespace, retains punctuation and attributes, writes coincident
|
|
168
168
|
markers together, and preserves all unrelated relationships and caption information.
|
|
169
169
|
- `@hypit/temporal-markup` owns the interpretation of each time form and declares its write targets.
|
|
170
170
|
Direct semantic references expose semantic targets; quoted time expressions expose parameter
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# `@hypit/video-cli`
|
|
2
2
|
|
|
3
3
|
Official video command application. It selects the Markup compiler Host and supplies one editable
|
|
4
|
-
starter Runtime Profile.
|
|
5
|
-
|
|
4
|
+
starter Runtime Profile. Execution Endpoints are selected by Profile `use` entries; Source imports
|
|
5
|
+
activate author packages. Immediate tools own their temporary input/output resource storage.
|
|
6
6
|
|
|
7
7
|
Every Frontend, Surface, deterministic Producer and Validator is activated from Source imports.
|
|
8
8
|
Installing a new author package therefore does not require a video CLI or Core release. Source
|
|
@@ -67,9 +67,35 @@ to the chosen file. `measure` estimates a passage locally:
|
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
69
|
hypit transcribe reference.mp4 --to notes/reference.transcript.json --language en
|
|
70
|
+
hypit transcribe assets/recorded-voice.wav --to notes/voice.transcript.json --language en
|
|
70
71
|
hypit measure main.svml --segment hook --language en --pace normal --rounding round
|
|
71
72
|
```
|
|
72
73
|
|
|
74
|
+
`snapshot` follows the same immediate invocation model for picture inspection. Prefer it for
|
|
75
|
+
existing production states and motion sequences, keeping Studio for playback with sound and Builds
|
|
76
|
+
for encoded delivery:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
hypit snapshot --studio http://localhost:5191 --at-frame 240,255,269 --to evidence/states
|
|
80
|
+
hypit snapshot --studio http://localhost:5191 --start-frame 240 --end-frame-exclusive 270 \
|
|
81
|
+
--grid 4x3 --cell 480 --to evidence/motion
|
|
82
|
+
hypit snapshot ./picture/index.html --at-frame 240 --to evidence/detail
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`--studio` reads Studio's current compiled document and its declared resources; a path or HTML URL
|
|
86
|
+
reads materialized HTML with inline scripts/styles and directly addressed media/fonts. The Profile's
|
|
87
|
+
`@hypit/render-hyperframes@1#render-frames` Endpoint returns PNGs in selected-frame order.
|
|
88
|
+
`--runtime` and `--workspace` select the environment just as for `transcribe`. The call streams
|
|
89
|
+
resources through a temporary `FileResourceStore`, writes full-size PNGs and optional grid pages,
|
|
90
|
+
then releases that temporary storage. `--to` names a new directory. `--json` reports paths and original
|
|
91
|
+
frame positions. Grid labels remain outside the picture. No Build, Worker receipt or video encoding
|
|
92
|
+
is created. Provider-owned browser preparation is unchanged.
|
|
93
|
+
|
|
94
|
+
`media frames --every-frame` and `media tiles --every-frame` decode every source frame in a selected
|
|
95
|
+
half-open seconds interval once. Native timestamps, including variable frame rate, are read from
|
|
96
|
+
decoder PTS and time base. `tiles --ranges <json> --every-frame` decodes each listed interval and
|
|
97
|
+
paginates its images. `--transcript` adds word context. This native path uses no FPS resampling.
|
|
98
|
+
|
|
73
99
|
For `transcribe`, set `--language` to an explicit lowercase two- or three-letter spoken language code,
|
|
74
100
|
such as `en`, `zh` or `ko`. The selected service owns which languages it can align. Chinese speech uses `zh`, including
|
|
75
101
|
Chinese speech containing English names. The request selects the recognition language and
|
|
@@ -106,6 +132,9 @@ prints what a Source may write:
|
|
|
106
132
|
```bash
|
|
107
133
|
hypit media probe reference.mp4
|
|
108
134
|
hypit media cut reference.mp4 --start 12 --end 19.5 --label-time --to notes/hook.mp4
|
|
135
|
+
hypit media cut assets/talk.mp4 --start 12 --end 19.5 --to assets/opening.mp4
|
|
136
|
+
hypit media cut assets/talk.mp4 --keep 12:15.5 --keep 16:19.5 --to assets/opening-edited.mp4
|
|
137
|
+
hypit media cut assets/narration.wav --keep 0.3:4.1 --keep 4.6:9.2 --to assets/narration-edited.wav
|
|
109
138
|
hypit media frames reference.mp4 --at 12.4,13.1 --label-time --to notes/hook-frames
|
|
110
139
|
hypit media tile reference.mp4 --start 12 --end 19.5 --to notes/hook-grid.jpg
|
|
111
140
|
hypit media tile reference.mp4 --at 12.4,13.1,14.8 --columns 3 --to notes/exact-grid.jpg
|
|
@@ -119,7 +148,21 @@ hypit vocabulary @hypit/media-pipeline --tag StillVideo
|
|
|
119
148
|
hypit vocabulary --visual text
|
|
120
149
|
```
|
|
121
150
|
|
|
122
|
-
`
|
|
151
|
+
`probe` accepts audio-only files as well as video. `cut` keeps one interval using `--start` and
|
|
152
|
+
`--end`, or joins explicitly retained, ordered, non-overlapping intervals using repeated
|
|
153
|
+
`--keep start:end` in source seconds. The latter is useful for removing gaps inside one recorded
|
|
154
|
+
performance; it does not decide where Script Segments belong. Video retains its available picture
|
|
155
|
+
and sound together (MP4 is a useful output container); audio-only outputs PCM WAV. A silent source
|
|
156
|
+
video stays silent. The
|
|
157
|
+
`--json` reports the source intervals, their nominal positions on the new local clock, and the
|
|
158
|
+
measured output duration; actual frame and sample boundaries can differ slightly from the nominal
|
|
159
|
+
positions. It writes a new file and refuses to
|
|
160
|
+
overwrite an existing one. `--label-time` retains its single-video-interval role: it visibly
|
|
161
|
+
overlays source time on an inspection copy, not on the clean production media.
|
|
162
|
+
|
|
163
|
+
`transcribe` also accepts audio or video. Its transcript refers to the *input file's* clock. A
|
|
164
|
+
cut or joined file has a new clock; use the final recorded performance and its Script in the
|
|
165
|
+
Build's semantic preparation rather than treating source transcript timestamps as final timing.
|
|
123
166
|
`frames` writes one JPEG per requested time, selecting the first decoded frame at or after it.
|
|
124
167
|
Visible frame labels use that frame's actual timestamp, as do the labels below each `tile` cell.
|
|
125
168
|
Sampling is shared by `frames`, `tile` and `tiles`:
|
|
@@ -149,7 +192,7 @@ and its Endpoint remain separate. `boundaries` reports adjacent-frame
|
|
|
149
192
|
change candidates and their measured scores; it does not suppress short changes or call them shots.
|
|
150
193
|
`prepare-fetch` explicitly prepares the locked downloader environment; `fetch` requires it and
|
|
151
194
|
turns a link into a file with the pinned yt-dlp; [the downloader package](../yt-dlp/README.md)
|
|
152
|
-
owns its dependencies, download choices and file handling. Commands that create
|
|
195
|
+
owns its dependencies, download choices and file handling. Commands that create files write only
|
|
153
196
|
what `--to` names and refuse to overwrite. `vocabulary` reads the installed
|
|
154
197
|
manifests: every package with its tags and models, or one package's Surfaces with their attributes,
|
|
155
198
|
children and example, or the value shapes a drawing Producer must emit.
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"@hypit/driver-node": "workspace:*",
|
|
19
19
|
"@hypit/estimate": "workspace:*",
|
|
20
20
|
"@hypit/generation": "workspace:*",
|
|
21
|
+
"@hypit/hyperframes": "workspace:*",
|
|
21
22
|
"@hypit/markup": "workspace:*",
|
|
22
23
|
"@hypit/model-kit": "workspace:*",
|
|
23
24
|
"@hypit/package-loader-node": "workspace:*",
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
"@hypit/provider-monid": "workspace:*",
|
|
29
30
|
"@hypit/provider-pollo": "workspace:*",
|
|
30
31
|
"@hypit/provider-tokendance": "workspace:*",
|
|
32
|
+
"@hypit/render-hyperframes": "workspace:*",
|
|
33
|
+
"@hypit/resource-store-fs": "workspace:*",
|
|
31
34
|
"@hypit/runtime": "workspace:*",
|
|
32
35
|
"@hypit/runtime-kit": "workspace:*",
|
|
33
36
|
"@hypit/runtime-host-node": "workspace:*",
|
|
@@ -4,8 +4,10 @@ import type { CliIo } from "@hypit/cli";
|
|
|
4
4
|
import { creationCommands, isCreationCommand, writeCreationHelp } from "./creation.js";
|
|
5
5
|
import { isMediaCommand, mediaCommands, writeMediaHelp } from "./media.js";
|
|
6
6
|
import { writeVocabularyHelp } from "./vocabulary.js";
|
|
7
|
+
import { writeSnapshotHelp } from "./snapshot.js";
|
|
7
8
|
import { writeCaptureHelp } from "./capture.js";
|
|
8
9
|
import { runVersionCli, writeVersionHelp } from "./version.js";
|
|
10
|
+
import { acceptSecretBytes } from "./secret-input.js";
|
|
9
11
|
|
|
10
12
|
const argv = process.argv.slice(2);
|
|
11
13
|
const json = argv.includes("--json");
|
|
@@ -26,22 +28,19 @@ async function readSecret(prompt: string): Promise<string> {
|
|
|
26
28
|
}
|
|
27
29
|
process.stderr.write(prompt);
|
|
28
30
|
return await new Promise<string>((resolve, reject) => {
|
|
29
|
-
|
|
31
|
+
const raw: number[] = [];
|
|
30
32
|
const finish = (error?: Error): void => {
|
|
31
33
|
process.stdin.off("data", input);
|
|
32
34
|
process.stdin.setRawMode(false);
|
|
33
35
|
process.stdin.pause();
|
|
34
36
|
process.stderr.write("\n");
|
|
35
|
-
if (error === undefined) resolve(
|
|
37
|
+
if (error === undefined) resolve(Buffer.from(raw).toString("utf8"));
|
|
36
38
|
else reject(error);
|
|
37
39
|
};
|
|
38
40
|
const input = (chunk: Buffer): void => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (byte === 8 || byte === 127) { value = value.slice(0, -1); continue; }
|
|
43
|
-
value += String.fromCharCode(byte);
|
|
44
|
-
}
|
|
41
|
+
const result = acceptSecretBytes(raw, chunk);
|
|
42
|
+
if (result === "cancelled") finish(new Error("credential input cancelled"));
|
|
43
|
+
if (result === "done") finish();
|
|
45
44
|
};
|
|
46
45
|
process.stdin.setRawMode(true);
|
|
47
46
|
process.stdin.resume();
|
|
@@ -68,6 +67,7 @@ async function main(): Promise<void> {
|
|
|
68
67
|
const topic = argv[0] === "help" ? argv[1]
|
|
69
68
|
: argv[0] === "--help" ? undefined
|
|
70
69
|
: argv.includes("--help") ? argv[0] : undefined;
|
|
70
|
+
if (topic === "snapshot") { writeSnapshotHelp(io); return; }
|
|
71
71
|
if (topic === "version") { writeVersionHelp(io); return; }
|
|
72
72
|
if (isCreationCommand(topic)) {
|
|
73
73
|
writeCreationHelp(io, topic);
|
|
@@ -89,7 +89,7 @@ async function main(): Promise<void> {
|
|
|
89
89
|
writeCliHelp(io, topic);
|
|
90
90
|
if (topic === undefined) {
|
|
91
91
|
io.write(`\nInstallation\n version [--check] [--registry <url>] [--json]\n\nCreation tools (one request through the selected Runtime Profile, no Build)\n${
|
|
92
|
-
creationCommands.map((item) => ` ${item}`).join("\n")}\n hypit help <tool> for each\n`
|
|
92
|
+
[...creationCommands, "snapshot"].map((item) => ` ${item}`).join("\n")}\n hypit help <tool> for each\n`
|
|
93
93
|
+ `\nStudio\n studio --run <build.svrun>\n hypit studio --help for session options\n\nPreparation (local tools and project files)\n media ${mediaCommands.join(" | ")}\n capture screenshot | run | install-browser\n vocabulary\n hypit help media, hypit help capture, hypit help vocabulary\n`);
|
|
94
94
|
}
|
|
95
95
|
return;
|
|
@@ -150,7 +150,7 @@ function capabilityName(capability: CapabilityRef): string {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/** Which Endpoint will serve this call and where its Provider publishes prices, before spending. */
|
|
153
|
-
async function selectedProvider(host: CreationHost, need: Need, profile: string): Promise<RuntimeHostCapabilityProvider> {
|
|
153
|
+
export async function selectedProvider(host: CreationHost, need: Need, profile: string): Promise<RuntimeHostCapabilityProvider> {
|
|
154
154
|
const [provider] = await host.providers([{
|
|
155
155
|
request: need.id,
|
|
156
156
|
capability: need.capability,
|
|
@@ -176,7 +176,7 @@ function priceLine(provider: RuntimeHostCapabilityProvider): string {
|
|
|
176
176
|
: provider.pricing.kind === "local" ? "local, no Provider charge" : provider.pricing.url;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
function providerView(provider: RuntimeHostCapabilityProvider) {
|
|
179
|
+
export function providerView(provider: RuntimeHostCapabilityProvider) {
|
|
180
180
|
return {
|
|
181
181
|
endpoint: provider.endpoint ?? null,
|
|
182
182
|
use: provider.use ?? null,
|
|
@@ -184,7 +184,7 @@ function providerView(provider: RuntimeHostCapabilityProvider) {
|
|
|
184
184
|
};
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
function providerLine(provider: RuntimeHostCapabilityProvider): string {
|
|
187
|
+
export function providerLine(provider: RuntimeHostCapabilityProvider): string {
|
|
188
188
|
return `${provider.endpoint} (${provider.use}) · ${priceLine(provider)}`;
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sharp from "sharp";
|
|
2
|
+
|
|
3
|
+
/** Arrange already captured evidence. Decoding and time selection belong to the caller. */
|
|
4
|
+
export async function writeFrameGrid(
|
|
5
|
+
frames: readonly { readonly path: string; readonly label: Buffer }[],
|
|
6
|
+
target: string, cellWidth: number, columns: number,
|
|
7
|
+
): Promise<void> {
|
|
8
|
+
if (frames.length === 0) throw new Error("A frame grid needs at least one image");
|
|
9
|
+
const cells = [];
|
|
10
|
+
for (const frame of frames) {
|
|
11
|
+
const picture = await sharp(frame.path).resize({ width: cellWidth }).png().toBuffer({ resolveWithObject: true });
|
|
12
|
+
const labelHeight = (await sharp(frame.label).metadata()).height!;
|
|
13
|
+
cells.push({ picture: picture.data, height: picture.info.height, label: frame.label, labelHeight });
|
|
14
|
+
}
|
|
15
|
+
const pictureHeight = Math.max(...cells.map(cell => cell.height));
|
|
16
|
+
const rowHeight = pictureHeight + Math.max(...cells.map(cell => cell.labelHeight));
|
|
17
|
+
const rows = Math.ceil(cells.length / columns);
|
|
18
|
+
await sharp({ create: {
|
|
19
|
+
width: columns * (cellWidth + 8) + 8, height: rows * (rowHeight + 8) + 8,
|
|
20
|
+
channels: 3, background: "#0c0c0c",
|
|
21
|
+
} }).composite(cells.flatMap((cell, index) => {
|
|
22
|
+
const left = 8 + index % columns * (cellWidth + 8);
|
|
23
|
+
const top = 8 + Math.floor(index / columns) * (rowHeight + 8);
|
|
24
|
+
return [{ input: cell.picture, left, top }, { input: cell.label, left, top: top + pictureHeight }];
|
|
25
|
+
})).toFile(target);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function snapshotFrameLabel(frame: number, seconds: number, width: number): Promise<Buffer> {
|
|
29
|
+
const text = await sharp({ text: { text: `<span foreground="#f5f5f5">Frame ${frame} · ${seconds.toFixed(6)} s</span>`,
|
|
30
|
+
font: `sans ${Math.max(12, Math.round(width / 28))}`, width: width - 16, rgba: true,
|
|
31
|
+
} }).png().toBuffer({ resolveWithObject: true });
|
|
32
|
+
return sharp({ create: { width, height: text.info.height + 16, channels: 3, background: "#0c0c0c" } })
|
|
33
|
+
.composite([{ input: text.data, left: 8, top: 8 }]).png().toBuffer();
|
|
34
|
+
}
|
|
@@ -12,6 +12,7 @@ import { videoCliDistribution } from "./distribution.js";
|
|
|
12
12
|
import { runMediaCli } from "./media.js";
|
|
13
13
|
import { runVocabularyCli } from "./vocabulary.js";
|
|
14
14
|
import { runCaptureCli } from "./capture.js";
|
|
15
|
+
import { runSnapshotCli } from "./snapshot.js";
|
|
15
16
|
import { runVersionCli } from "./version.js";
|
|
16
17
|
|
|
17
18
|
export {
|
|
@@ -42,6 +43,7 @@ export function runVideoCli(
|
|
|
42
43
|
: [videoCliDistribution.packageRoot]);
|
|
43
44
|
installExternalPackageResolution([hypitHostPackageRoot()]);
|
|
44
45
|
if (isCreationCommand(argv[0])) return runCreationCli(argv, io);
|
|
46
|
+
if (argv[0] === "snapshot") return runSnapshotCli(argv, io);
|
|
45
47
|
if (argv[0] === "media") return runMediaCli(argv, io);
|
|
46
48
|
if (argv[0] === "capture") return runCaptureCli(argv, io);
|
|
47
49
|
if (argv[0] === "vocabulary") return runVocabularyCli(argv, io);
|
|
@@ -50,3 +52,5 @@ export function runVideoCli(
|
|
|
50
52
|
bootstrapPackages: packages,
|
|
51
53
|
});
|
|
52
54
|
}
|
|
55
|
+
|
|
56
|
+
export { runSnapshotCli, writeSnapshotHelp } from "./snapshot.js";
|