versioncam 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +174 -0
- package/README.md +152 -15
- package/dist/cli/commands/check.js +45 -4
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/doctor.js +39 -2
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/init.js +25 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/preview.js +10 -1
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/commands/publish.d.ts +17 -0
- package/dist/cli/commands/publish.js +203 -0
- package/dist/cli/commands/publish.js.map +1 -0
- package/dist/cli/commands/record.js +2 -0
- package/dist/cli/commands/record.js.map +1 -1
- package/dist/cli/commands/render.d.ts +17 -0
- package/dist/cli/commands/render.js +98 -21
- package/dist/cli/commands/render.js.map +1 -1
- package/dist/cli/cwd.d.ts +46 -0
- package/dist/cli/cwd.js +102 -0
- package/dist/cli/cwd.js.map +1 -0
- package/dist/cli/main.js +19 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/usage.d.ts +3 -2
- package/dist/cli/usage.js +17 -4
- package/dist/cli/usage.js.map +1 -1
- package/dist/config.d.ts +35 -1
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -1
- package/dist/core/camera-track.d.ts +63 -0
- package/dist/core/camera-track.js +230 -0
- package/dist/core/camera-track.js.map +1 -0
- package/dist/core/camera.d.ts +14 -1
- package/dist/core/camera.js +18 -2
- package/dist/core/camera.js.map +1 -1
- package/dist/core/follow.d.ts +129 -0
- package/dist/core/follow.js +151 -0
- package/dist/core/follow.js.map +1 -0
- package/dist/core/motion-defaults.d.ts +29 -0
- package/dist/core/motion-defaults.js +29 -0
- package/dist/core/motion-defaults.js.map +1 -1
- package/dist/core/timeline.d.ts +10 -2
- package/dist/core/timeline.js.map +1 -1
- package/dist/driver/clip.d.ts +6 -0
- package/dist/driver/clip.js +6 -3
- package/dist/driver/clip.js.map +1 -1
- package/dist/driver/reports.d.ts +34 -2
- package/dist/driver/reports.js +31 -3
- package/dist/driver/reports.js.map +1 -1
- package/dist/driver/session.d.ts +36 -1
- package/dist/driver/session.js +72 -14
- package/dist/driver/session.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +7 -1
- package/dist/loader.js +23 -8
- package/dist/loader.js.map +1 -1
- package/dist/page/assets/index-fZtacWyp.js +2 -0
- package/dist/page/index.html +1 -1
- package/dist/publish/client.d.ts +38 -0
- package/dist/publish/client.js +118 -0
- package/dist/publish/client.js.map +1 -0
- package/dist/publish/index.d.ts +12 -0
- package/dist/publish/index.js +11 -0
- package/dist/publish/index.js.map +1 -0
- package/dist/publish/manifest.d.ts +46 -0
- package/dist/publish/manifest.js +133 -0
- package/dist/publish/manifest.js.map +1 -0
- package/dist/publish/protocol.d.ts +203 -0
- package/dist/publish/protocol.js +56 -0
- package/dist/publish/protocol.js.map +1 -0
- package/dist/render/encode.d.ts +27 -1
- package/dist/render/encode.js +115 -37
- package/dist/render/encode.js.map +1 -1
- package/dist/render/presentation.d.ts +12 -0
- package/dist/render/presentation.js +15 -0
- package/dist/render/presentation.js.map +1 -1
- package/dist/render/render.js +19 -1
- package/dist/render/render.js.map +1 -1
- package/dist/render/sampling.d.ts +13 -0
- package/dist/render/sampling.js +47 -19
- package/dist/render/sampling.js.map +1 -1
- package/dist/render/scenes.d.ts +16 -0
- package/dist/render/scenes.js +108 -0
- package/dist/render/scenes.js.map +1 -0
- package/dist/render/sequence.d.ts +21 -1
- package/dist/render/sequence.js +71 -14
- package/dist/render/sequence.js.map +1 -1
- package/dist/review/review.js +46 -21
- package/dist/review/review.js.map +1 -1
- package/dsl.md +37 -0
- package/package.json +6 -2
- package/plugin/README.md +7 -2
- package/plugin/agents/versioncam-reviewer.md +3 -2
- package/plugin/skills/versioncam/SKILL.md +60 -14
- package/plugin/skills/versioncam/authoring.md +6 -1
- package/plugin/skills/versioncam/onboarding.md +17 -17
- package/dist/page/assets/index-DUom5amc.js +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { defineRecorder, outputSize, resolveConfig } from "./config.js";
|
|
2
|
-
export type { AuthConfig, CaptureConfig, CursorStart, ReviewConfig, Motion, Presentation, RecorderConfig, RenderConfig, ResolvedConfig, ResolvedWebServer, SettleConfig, WebServerConfig, } from "./config.js";
|
|
2
|
+
export type { AuthConfig, CaptionInset, CaptureConfig, CursorStart, ReviewConfig, Motion, Presentation, RecorderConfig, RenderConfig, ResolvedConfig, ResolvedWebServer, SettleConfig, WebServerConfig, } from "./config.js";
|
|
3
3
|
export { ensureApp, withApp } from "./app-server.js";
|
|
4
4
|
export type { AppHandle } from "./app-server.js";
|
|
5
|
-
export { clip, runClip } from "./driver/clip.js";
|
|
5
|
+
export { clip, runClip, RECORDER_VERSION } from "./driver/clip.js";
|
|
6
6
|
export type { RunClipOptions } from "./driver/clip.js";
|
|
7
7
|
export type { ClipBody, ClipDefinition, ClipOptions, RunClipResult, } from "./driver/clip.js";
|
|
8
8
|
export type { RecordingSession, SessionOptions } from "./driver/session.js";
|
|
@@ -22,9 +22,17 @@ export type { Scene, Size } from "./scene.js";
|
|
|
22
22
|
export { loadClips, loadConfig, loadSceneIds, bundleScenes } from "./loader.js";
|
|
23
23
|
export { contactSheet, renderFrame, renderRecording, renderScene, withBrowser, readTimeline, } from "./render/render.js";
|
|
24
24
|
export type { RenderOutcome, RenderTarget } from "./render/render.js";
|
|
25
|
-
export { beatFrame, frameAtMark, markedFrames, uniformFrames, SHEET_TILES, } from "./render/sampling.js";
|
|
25
|
+
export { beatFrame, evenFrames, frameAtMark, markedFrames, uniformFrames, SHEET_TILES, } from "./render/sampling.js";
|
|
26
26
|
export { compareRecordings, frameHashes, readRecording, stateFileAt, } from "./driver/compare.js";
|
|
27
27
|
export type { Comparison, RecordingRef } from "./driver/compare.js";
|
|
28
28
|
export { formatReview, reviewRecording, DEFAULT_BOUNDS, } from "./review/review.js";
|
|
29
29
|
export type { Beat, ReviewBounds, ReviewInput, ReviewItem, ReviewResult, } from "./review/review.js";
|
|
30
30
|
export { buildSequence } from "./render/sequence.js";
|
|
31
|
+
export { validateTimeline } from "./core/timeline.js";
|
|
32
|
+
export type { Step } from "./core/timeline.js";
|
|
33
|
+
export { publishRecordings, PublishRefused } from "./publish/client.js";
|
|
34
|
+
export type { PublishFiles, PublishOptions } from "./publish/client.js";
|
|
35
|
+
export { publishedRenderConfig, readClip, pngSize, sha256Hex, } from "./publish/manifest.js";
|
|
36
|
+
export type { RecordingFiles } from "./publish/manifest.js";
|
|
37
|
+
export * from "./publish/protocol.js";
|
|
38
|
+
export type { Failure, Targets } from "./driver/reports.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { defineRecorder, outputSize, resolveConfig } from "./config.js";
|
|
2
2
|
export { ensureApp, withApp } from "./app-server.js";
|
|
3
|
-
export { clip, runClip } from "./driver/clip.js";
|
|
3
|
+
export { clip, runClip, RECORDER_VERSION } from "./driver/clip.js";
|
|
4
4
|
export { createGate } from "./driver/gate.js";
|
|
5
5
|
export { launchBrowser, DETERMINISTIC_ARGS } from "./driver/launch.js";
|
|
6
6
|
export { formatTiming } from "./core/timing.js";
|
|
@@ -9,8 +9,12 @@ export { sseBody } from "./core/sse.js";
|
|
|
9
9
|
export { MOTION } from "./core/motion-defaults.js";
|
|
10
10
|
export { loadClips, loadConfig, loadSceneIds, bundleScenes } from "./loader.js";
|
|
11
11
|
export { contactSheet, renderFrame, renderRecording, renderScene, withBrowser, readTimeline, } from "./render/render.js";
|
|
12
|
-
export { beatFrame, frameAtMark, markedFrames, uniformFrames, SHEET_TILES, } from "./render/sampling.js";
|
|
12
|
+
export { beatFrame, evenFrames, frameAtMark, markedFrames, uniformFrames, SHEET_TILES, } from "./render/sampling.js";
|
|
13
13
|
export { compareRecordings, frameHashes, readRecording, stateFileAt, } from "./driver/compare.js";
|
|
14
14
|
export { formatReview, reviewRecording, DEFAULT_BOUNDS, } from "./review/review.js";
|
|
15
15
|
export { buildSequence } from "./render/sequence.js";
|
|
16
|
+
export { validateTimeline } from "./core/timeline.js";
|
|
17
|
+
export { publishRecordings, PublishRefused } from "./publish/client.js";
|
|
18
|
+
export { publishedRenderConfig, readClip, pngSize, sha256Hex, } from "./publish/manifest.js";
|
|
19
|
+
export * from "./publish/protocol.js";
|
|
16
20
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAgBxE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAGrD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AASnE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGhD,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE7E,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAenD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EACL,YAAY,EACZ,WAAW,EACX,eAAe,EACf,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,SAAS,EACT,UAAU,EACV,WAAW,EACX,YAAY,EACZ,aAAa,EACb,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,YAAY,EACZ,eAAe,EACf,cAAc,GACf,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAExE,OAAO,EACL,qBAAqB,EACrB,QAAQ,EACR,OAAO,EACP,SAAS,GACV,MAAM,uBAAuB,CAAC;AAE/B,cAAc,uBAAuB,CAAC"}
|
package/dist/loader.d.ts
CHANGED
|
@@ -24,5 +24,11 @@ export declare function loadConfig(cwd: string): Promise<ResolvedConfig>;
|
|
|
24
24
|
export declare function loadClips(config: ResolvedConfig, only?: string[]): Promise<ClipDefinition[]>;
|
|
25
25
|
/** The app's scenes, bundled for the browser. Null when it has none. */
|
|
26
26
|
export declare function bundleScenes(config: ResolvedConfig): Promise<string | null>;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Scene ids, without running a browser: needed to list and to filter.
|
|
29
|
+
*
|
|
30
|
+
* Reading them is also the check that the module exports its scenes where
|
|
31
|
+
* the render page will look for them. A module that does not is refused here,
|
|
32
|
+
* before anything renders, rather than left out of the output unremarked.
|
|
33
|
+
*/
|
|
28
34
|
export declare function loadSceneIds(config: ResolvedConfig): Promise<string[]>;
|
package/dist/loader.js
CHANGED
|
@@ -5,6 +5,7 @@ import { mkdir } from "node:fs/promises";
|
|
|
5
5
|
import { basename, dirname, join, resolve } from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { resolveConfig } from "./config.js";
|
|
8
|
+
import { readScenes } from "./render/scenes.js";
|
|
8
9
|
/**
|
|
9
10
|
* Loads the TypeScript an app writes: its config, its clips, its scenes.
|
|
10
11
|
*
|
|
@@ -123,21 +124,35 @@ export async function loadClips(config, only = []) {
|
|
|
123
124
|
? clips
|
|
124
125
|
: clips.filter((clip) => only.some((needle) => clip.id.includes(needle)));
|
|
125
126
|
}
|
|
126
|
-
/** The
|
|
127
|
-
|
|
127
|
+
/** The config's scenes module, or null when it names none. */
|
|
128
|
+
function scenesFile(config) {
|
|
128
129
|
if (!config.scenes)
|
|
129
130
|
return null;
|
|
130
131
|
if (!existsSync(config.scenes)) {
|
|
131
132
|
throw new Error(`versioncam config: \`scenes\` points at a missing file: ${config.scenes}`);
|
|
132
133
|
}
|
|
133
|
-
return
|
|
134
|
+
return config.scenes;
|
|
135
|
+
}
|
|
136
|
+
/** The app's scenes, bundled for the browser. Null when it has none. */
|
|
137
|
+
export async function bundleScenes(config) {
|
|
138
|
+
const file = scenesFile(config);
|
|
139
|
+
return file ? bundleModule(file, "browser", config.cacheDir) : null;
|
|
134
140
|
}
|
|
135
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Scene ids, without running a browser: needed to list and to filter.
|
|
143
|
+
*
|
|
144
|
+
* Reading them is also the check that the module exports its scenes where
|
|
145
|
+
* the render page will look for them. A module that does not is refused here,
|
|
146
|
+
* before anything renders, rather than left out of the output unremarked.
|
|
147
|
+
*/
|
|
136
148
|
export async function loadSceneIds(config) {
|
|
137
|
-
|
|
149
|
+
const file = scenesFile(config);
|
|
150
|
+
if (!file)
|
|
138
151
|
return [];
|
|
139
|
-
const bundle = await bundleModule(
|
|
140
|
-
const
|
|
141
|
-
|
|
152
|
+
const bundle = await bundleModule(file, "node", config.cacheDir);
|
|
153
|
+
const read = readScenes(await importFresh(bundle), file);
|
|
154
|
+
if ("error" in read)
|
|
155
|
+
throw new Error(read.error);
|
|
156
|
+
return Object.keys(read.scenes);
|
|
142
157
|
}
|
|
143
158
|
//# sourceMappingURL=loader.js.map
|
package/dist/loader.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();function e(e){let n=t(e);return n*n*n*(10+n*(-15+6*n))}function t(e){return e<=0?0:e>=1?1:e}function n(e,t,n){return e+(t-e)*n}function r(e,t,r){let i=e.x+e.w/2,a=e.y+e.h/2,o=t.x+t.w/2,s=t.y+t.h/2,c=e.w*(t.w/e.w)**+r,l=e.h*(t.h/e.h)**+r,u=n(i,o,r),d=n(a,s,r);return{x:u-c/2,y:d-l/2,w:c,h:l}}function i(e,t,r){return{x:n(e.x,t.x,r),y:n(e.y,t.y,r),w:n(e.w,t.w,r),h:n(e.h,t.h,r)}}function a(t,n){if(t.length===0)throw Error(`evalCamera called with no keys`);if(n<=t[0].t)return t[0].rect;for(let a=1;a<t.length;a+=1){let o=t[a-1],s=t[a];if(n>=s.t)continue;if(s.ease===`hold`)return o.rect;let c=s.t-o.t,l=c<=0?1:(n-o.t)/c;return s.ease===`linear`?i(o.rect,s.rect,l):r(o.rect,s.rect,e(l))}return t[t.length-1].rect}function o(e,t,n,r){let i=1-r,a=i*i,o=2*i*r,s=r*r;return[a*e[0]+o*t[0]+s*n[0],a*e[1]+o*t[1]+s*n[1]]}function s(t,n){if(t.length===0)return[0,0];let r=t[0];if(n<=r.t0)return r.kind===`move`?r.from:r.at;for(let r of t){if(n>r.t1)continue;if(r.kind===`hold`)return r.at;let t=r.t1-r.t0,i=t<=0?1:(n-r.t0)/t;return o(r.from,r.ctrl,r.to,e(i))}let i=t[t.length-1];return i.kind===`move`?i.to:i.at}function c(e,t,n){let r=n.w/t.w;return[n.x+(e[0]-t.x)*r,n.y+(e[1]-t.y)*r]}function l(e,t,n){let r=n.w/t.w;return{x:n.x+(e.x-t.x)*r,y:n.y+(e.y-t.y)*r,w:e.w*r,h:e.h*r}}function u(e,t){return{x:e.x*t,y:e.y*t,w:e.w*t,h:e.h*t}}function d(e,t,n){let r=Math.min(e.width,e.height)*n,i=e.width-r*2,a=e.height-r*2,o=t.width/t.height,s=i,c=s/o;return c>a&&(c=a,s=c*o),{x:(e.width-s)/2,y:(e.height-c)/2,w:s,h:c}}function f(e,n,r){return r<=0?1:t((e-n+1)/r)}function p(e,n,r,i){return e<=n||e>=r?0:i<=0?1:Math.min(t((e-n)/i),t((r-e)/i))}function ee(e,t){return e/t}function te(e,t,n){let r=n?.padding??0;return!n||r<=0?{x:0,y:0,w:e.width,h:e.height}:d(e,t,r)}function m(e){let t=e?.caption??`auto`;return t===`none`?`none`:t===`below`||t===`overlay`?t:(e?.padding??0)>0?`below`:`overlay`}function h(e,t){let n=e?.captionInset;return n?.right===void 0?{x:n?.left??40,align:`left`}:{x:t-n.right,align:`right`}}var g={radius:16,hairline:`rgba(255, 255, 255, 0.16)`,shadow:`rgba(0, 0, 0, 0.45)`,shadowBlur:60,shadowOffsetY:18,captionFont:`600 30px "Helvetica Neue", Helvetica, Arial, "Liberation Sans", sans-serif`,captionColor:`rgba(255, 255, 255, 0.94)`,captionShadow:`rgba(0, 0, 0, 0.5)`,captionScrim:`rgba(0, 0, 0, 0.45)`,captionScrimHeight:86,cursorFill:`#ffffff`,cursorStroke:`rgba(0, 0, 0, 0.55)`,cursorShadow:`rgba(0, 0, 0, 0.35)`,ringWidth:3,ringRadius:10,spotlightDim:.55,spotlightTint:`6, 2, 16`};function ne(e,t){let n=/^#([0-9a-f]{6})$/i.exec(e.trim());if(!n)return e;let r=Number.parseInt(n[1],16);return`rgba(${r>>16&255}, ${r>>8&255}, ${r&255}, ${t})`}function _(e,t,n){let r=Math.min(n,t.w/2,t.h/2);e.beginPath(),e.moveTo(t.x+r,t.y),e.arcTo(t.x+t.w,t.y,t.x+t.w,t.y+t.h,r),e.arcTo(t.x+t.w,t.y+t.h,t.x,t.y+t.h,r),e.arcTo(t.x,t.y+t.h,t.x,t.y,r),e.arcTo(t.x,t.y,t.x+t.w,t.y,r),e.closePath()}function re(e,t,n){if(typeof n==`string`)e.fillStyle=n;else{let r=e.createLinearGradient(0,0,t.width,t.height);r.addColorStop(0,n[0]),r.addColorStop(1,n[1]),e.fillStyle=r}e.fillRect(0,0,t.width,t.height)}function ie(e,t,n){for(let r of e.cuts)if(t>r&&t<=r+n)return{current:e.frames[t],previous:e.frames[r],alpha:f(t,r+1,n)};return{current:e.frames[t],previous:null,alpha:1}}function v(e,t,n,r,i,a,o){let s=u(i,t.viewport.dpr),{current:c,previous:l,alpha:d}=ie(t,r,o.motion.crossfadeFrames),f=o.theme.presentation?.frame??!1;e.save(),f&&(_(e,a,g.radius),e.clip());let p=(t,r)=>{let i=n.images[t];i&&(e.globalAlpha=r,e.drawImage(i,s.x,s.y,s.w,s.h,a.x,a.y,a.w,a.h))};l!==null&&l!==c&&p(l,1),p(c,l===null?1:d),e.globalAlpha=1,e.restore()}function y(e,t){e.save(),e.shadowColor=g.shadow,e.shadowBlur=g.shadowBlur,e.shadowOffsetY=g.shadowOffsetY,e.fillStyle=`rgba(0,0,0,1)`,_(e,t,g.radius),e.fill(),e.restore()}function b(e,t){e.save(),e.strokeStyle=g.hairline,e.lineWidth=1,_(e,t,g.radius),e.stroke(),e.restore()}function x(e,t){return{x:e.x-t,y:e.y-t,w:e.w+t*2,h:e.h+t*2}}function S(e,t,n,r,i,a){let o=a.theme.presentation?.frame??!1;for(let s of t.highlights){let t=p(n,s.t0,s.t1,.25);if(t<=0)continue;let c=l(s.rect,r,i);e.save(),e.globalAlpha=t,s.style===`spotlight`?(e.save(),_(e,i,o?g.radius:0),e.clip(),e.fillStyle=`rgba(${g.spotlightTint}, ${g.spotlightDim})`,e.fillRect(i.x,i.y,i.w,i.h),e.globalCompositeOperation=`destination-out`,_(e,x(c,8),g.ringRadius),e.fill(),e.restore()):(e.strokeStyle=a.theme.accent,e.lineWidth=g.ringWidth,e.shadowColor=ne(a.theme.accent,.35),e.shadowBlur=18,_(e,x(c,6),g.ringRadius),e.stroke()),e.restore()}}function C(e,t,n){let r=!1,i=[],a=0;for(let o of e.pointer){if(o.t>t)break;if(o.kind===`down`)r=!0,a=o.t;else{r=!1;let e=t-o.t;e>=0&&e<=n.motion.rippleMs/1e3&&i.push({at:o.at,age:e})}}let o=r?t-a:0,s=r?Math.min(1,o/(n.motion.pressScaleMs/1e3)):0;return{scale:1-(1-n.motion.pressScale)*s,ripples:i}}function w(e,t,n,r,i){for(let a of t){let t=a.age/(i.motion.rippleMs/1e3),[o,s]=c(a.at,n,r);e.save(),e.globalAlpha=(1-t)*.55,e.strokeStyle=i.theme.accent,e.lineWidth=2.5,e.beginPath(),e.arc(o,s,6+t*i.motion.rippleRadius,0,Math.PI*2),e.stroke(),e.restore()}}function T(e,t,n,r){let i=r*n;e.save(),e.translate(t[0],t[1]),e.scale(i,i),e.shadowColor=g.cursorShadow,e.shadowBlur=6/i,e.shadowOffsetY=2/i,e.beginPath(),e.moveTo(0,0),e.lineTo(0,16.6),e.lineTo(4.1,12.9),e.lineTo(6.9,18.9),e.lineTo(9.6,17.6),e.lineTo(6.9,11.8),e.lineTo(12.1,11.5),e.closePath(),e.fillStyle=g.cursorFill,e.fill(),e.shadowColor=`transparent`,e.strokeStyle=g.cursorStroke,e.lineWidth=1/i,e.stroke(),e.restore()}function E(e,t,n,r,i,a){let o=m(a.theme.presentation);if(o!==`none`)for(let s of t.captions){let t=p(n,s.t0,s.t1,a.motion.captionFadeS);if(t<=0)continue;if(e.save(),e.globalAlpha=t,o===`overlay`){let t=g.captionScrimHeight,n=e.createLinearGradient(0,i.height-t,0,i.height);n.addColorStop(0,`rgba(0, 0, 0, 0)`),n.addColorStop(1,g.captionScrim),e.fillStyle=n,e.fillRect(0,i.height-t,i.width,t)}e.font=g.captionFont,e.fillStyle=g.captionColor,e.textBaseline=`middle`,e.shadowColor=g.captionShadow,e.shadowBlur=8;let c=r.x,l=(r.y+r.h+i.height)/2;if(o===`overlay`){let t=h(a.theme.presentation,i.width);e.textAlign=t.align,c=t.x,l=i.height-g.captionScrimHeight/2+4}e.fillText(s.text,c,l),e.restore()}}function ae(e,t,n,r,i,o){let l=ee(r,t.fps),u=a(t.camera,l),d=o.theme.presentation,f=te(i,t.viewport,d);d?.backdrop&&re(e,i,d.backdrop),d?.frame&&y(e,f),v(e,t,n,r,u,f,o),d?.frame&&b(e,f),S(e,t,l,u,f,o);let{scale:p,ripples:m}=C(t,l,o);w(e,m,u,f,o),T(e,c(s(t.cursor,l),u,f),p,o.theme.cursorScale),E(e,t,l,f,i,o)}var D="versioncam reads scenes from a module's default export: a Record<string, Scene> keyed by scene id, as in `export default { [intro.id]: intro }`.";function O(e){return e==null?String(e):Array.isArray(e)?`an array`:`a ${typeof e}`}function k(e){return typeof e==`string`?JSON.stringify(e):String(e)}function A(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function oe(e,t){if(!A(t))return`"${e}" is ${O(t)}, not a scene`;let{draw:n,durationFrames:r,fps:i}=t;return typeof n==`function`?!Number.isInteger(r)||r<1?`"${e}" has durationFrames ${k(r)}, where a whole number of frames, 1 or more, belongs`:typeof i!=`number`||!Number.isFinite(i)||i<=0?`"${e}" has fps ${k(i)}, where a number above 0 belongs`:null:`"${e}" has no draw(ctx, frame, canvas)`}function j(e){if(!A(e))return O(e);if(typeof e.draw==`function`)return`a single scene rather than a record of them`;for(let[t,n]of Object.entries(e)){let e=oe(t,n);if(e)return`a record in which ${e}`}return null}function M(e,t){if(e.default===void 0){let n=Object.keys(e).filter(e=>e!=="default"),r=[`${t} has no default export.`+(n.length>0?` It exports ${n.join(`, `)}.`:``),D],i=n.filter(t=>j(e[t])===null&&Object.keys(e[t]).length>0);return i.length===1&&r.push(`${i[0]} is one: add \`export default ${i[0]}\`.`),{error:r.join(`
|
|
2
|
+
`)}}let n=j(e.default);return n?{error:`${t} default-exports ${n}.\n${D}`}:{scenes:e.default}}function N(e,t,n){let r=e.map(e=>`"${e}"`).join(`, `),i=`No scene${e.length===1?``:`s`} called ${r}.`;return n===null?`${i} The config names no \`scenes\` module.`:`${i} ${n} defines ${t.length>0?t.join(`, `):`none`}.`}var P=`modulepreload`,F=function(e,t){return new URL(e,t).href},I={},L=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=F(t,n),t=s(t),t in I)return;I[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:P,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}).filter(e=>e!==void 0))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},R=new URLSearchParams(location.search),z=R.has(`recording`),B=R.get(`scene`),V=R.get(`headless`)===`1`;V&&document.body.classList.add(`headless`);var H=document.getElementById(`stage`),U=document.getElementById(`scrub`),W=document.getElementById(`status`),G=document.getElementById(`play`),K=document.getElementById(`error`);function q(e){throw K.textContent=e,Error(e)}async function J(e,t){let n=await fetch(e);return n.ok||q(`Could not load ${t} (${e}): ${n.status}`),await n.json()}async function se(e){let t=await fetch(e);return t.ok||q(`Could not load ${e}: ${t.status}`),createImageBitmap(await t.blob())}function Y(e){H.width=e.output.width,H.height=e.output.height,V?(H.style.width=`${e.output.width}px`,H.style.height=`${e.output.height}px`):(H.style.width=`min(100%, 1280px)`,H.style.height=`auto`);let t=H.getContext(`2d`,{alpha:!1});return t||q(`No 2D context`),t.imageSmoothingEnabled=!0,t.imageSmoothingQuality=`high`,t}function X(e,t,n,r){let i=!1;G.addEventListener(`click`,()=>{i=!i,G.textContent=i?`Pause`:`Play`;let a=performance.now(),o=s=>{i&&(s-a>=1e3/e&&(a=s,r((n()+1)%t)),requestAnimationFrame(o))};requestAnimationFrame(o)})}function Z(e,t,n,r){e.durationFrames=t,e.fps=n,e.setFrame=e=>new Promise(t=>{r(e),requestAnimationFrame(()=>requestAnimationFrame(()=>t()))})}async function Q(e,t){let n=await J(`/recording/timeline.json`,`the recording`),r=Y(t),i={images:await Promise.all(n.states.map(e=>se(`/recording/states/${e}`)))};await document.fonts.ready;let a=-1,o=e=>{let o=Math.max(0,Math.min(n.durationFrames-1,e));ae(r,n,i,o,H,t),a=o,V||(U.value=String(o),W.textContent=`${o+1} / ${n.durationFrames} · ${(o/n.fps).toFixed(2)}s · state ${n.frames[o]}`)};U.max=String(n.durationFrames-1),U.addEventListener(`input`,()=>o(Number(U.value))),X(n.fps,n.durationFrames,()=>a,o),o(Number(R.get(`frame`)??0)),Z(e,n.durationFrames,n.fps,o)}async function ce(e,t,n){t.scenes===null&&q(N([n],[],null));let r=M(await L(()=>import(`/scenes.js`),[],import.meta.url),t.scenes);`error`in r&&q(r.error);let i=r.scenes[n];i||q(N([n],Object.keys(r.scenes),t.scenes));let a=Y(t);await document.fonts.ready;let o=-1,s=e=>{let t=Math.max(0,Math.min(i.durationFrames-1,e));i.draw(a,t,H),o=t,V||(U.value=String(t),W.textContent=`${t+1} / ${i.durationFrames} · ${(t/i.fps).toFixed(2)}s`)};U.max=String(i.durationFrames-1),U.addEventListener(`input`,()=>s(Number(U.value))),X(i.fps,i.durationFrames,()=>o,s),s(Number(R.get(`frame`)??0)),Z(e,i.durationFrames,i.fps,s)}async function le(e){let t=await J(`/render-config.json`,`the render config`);if(B)return ce(e,t,B);if(z)return Q(e,t);q(`Pass ?recording or ?scene=<id>.`)}var $={ready:Promise.resolve(),setFrame:async()=>{},durationFrames:0,fps:60};window.__render=$,$.ready=le($);
|
package/dist/page/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<title>versioncam render</title>
|
|
6
6
|
<style>*{box-sizing:border-box;margin:0;padding:0;transition:none!important;animation:none!important}body{color:#e8e2f5;background:#0b0614;flex-direction:column;align-items:center;min-height:100vh;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;display:flex}body.headless{background:0 0;display:block}#stage{display:block}body.headless #stage{position:fixed;inset:0}#controls{align-items:center;gap:14px;width:min(100%,1280px);padding:14px 18px 28px;font-size:13px;display:flex}body.headless #controls{display:none}#scrub{flex:1}#status{font-variant-numeric:tabular-nums;white-space:nowrap;opacity:.75}button{font:inherit;color:#fff;cursor:pointer;background:#2f6fed;border:0;border-radius:6px;padding:6px 14px}#error{color:#ff9a9a;white-space:pre-wrap;padding:24px;font-family:ui-monospace,Menlo,monospace}</style>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-fZtacWyp.js"></script>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<canvas id="stage"></canvas>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type PublishBegin, type PublishBeginResponse, type PublishCommitResponse } from "./protocol.js";
|
|
2
|
+
/**
|
|
3
|
+
* The client half of the publish protocol: begin, upload what the service
|
|
4
|
+
* does not have yet, commit.
|
|
5
|
+
*
|
|
6
|
+
* It knows nothing about recordings on disk. `files` hands it bytes on
|
|
7
|
+
* request, which keeps it testable against a fake `fetch` and keeps a state
|
|
8
|
+
* the service already has from ever being read off the disk.
|
|
9
|
+
*/
|
|
10
|
+
/** Bytes for what the service asks for, looked up lazily. */
|
|
11
|
+
export type PublishFiles = {
|
|
12
|
+
state(hash: string): Promise<Uint8Array>;
|
|
13
|
+
timeline(clip: string): Promise<Uint8Array>;
|
|
14
|
+
targets(clip: string): Promise<Uint8Array>;
|
|
15
|
+
failure(clip: string): Promise<Uint8Array>;
|
|
16
|
+
};
|
|
17
|
+
export type PublishOptions = {
|
|
18
|
+
/** The API's origin, e.g. https://api.version.cam. */
|
|
19
|
+
api: string;
|
|
20
|
+
/** A GitHub OpenID Connect token, or a `vc_` project token. */
|
|
21
|
+
credential: string;
|
|
22
|
+
fetch?: typeof fetch;
|
|
23
|
+
/** How many uploads run at once. */
|
|
24
|
+
concurrency?: number;
|
|
25
|
+
/** Waits between retries, in milliseconds; the last one is the last try. */
|
|
26
|
+
backoffMs?: readonly number[];
|
|
27
|
+
log?: (line: string) => void;
|
|
28
|
+
};
|
|
29
|
+
/** An answer from the service that no retry will change. */
|
|
30
|
+
export declare class PublishRefused extends Error {
|
|
31
|
+
readonly status: number;
|
|
32
|
+
readonly code: string;
|
|
33
|
+
constructor(message: string, status: number, code: string);
|
|
34
|
+
}
|
|
35
|
+
export declare function publishRecordings(begin: PublishBegin, files: PublishFiles, options: PublishOptions): Promise<{
|
|
36
|
+
begun: PublishBeginResponse;
|
|
37
|
+
committed: PublishCommitResponse;
|
|
38
|
+
}>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { PUBLISH_PATHS, } from "./protocol.js";
|
|
2
|
+
/** An answer from the service that no retry will change. */
|
|
3
|
+
export class PublishRefused extends Error {
|
|
4
|
+
status;
|
|
5
|
+
code;
|
|
6
|
+
constructor(message, status, code) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function publishRecordings(begin, files, options) {
|
|
13
|
+
const call = caller(options);
|
|
14
|
+
const begun = (await call("POST", PUBLISH_PATHS.begin, {
|
|
15
|
+
body: JSON.stringify(begin),
|
|
16
|
+
type: "application/json",
|
|
17
|
+
}));
|
|
18
|
+
const uploads = [
|
|
19
|
+
...begun.needStates.map((hash) => async () => call("PUT", PUBLISH_PATHS.state(begun.publishId, hash), {
|
|
20
|
+
body: await files.state(hash),
|
|
21
|
+
type: "image/png",
|
|
22
|
+
})),
|
|
23
|
+
...begun.needTimelines.map((clip) => async () => call("PUT", PUBLISH_PATHS.timeline(begun.publishId, clip), {
|
|
24
|
+
body: await files.timeline(clip),
|
|
25
|
+
type: "application/json",
|
|
26
|
+
})),
|
|
27
|
+
...begun.needTargets.map((clip) => async () => call("PUT", PUBLISH_PATHS.targets(begun.publishId, clip), {
|
|
28
|
+
body: await files.targets(clip),
|
|
29
|
+
type: "application/json",
|
|
30
|
+
})),
|
|
31
|
+
...begun.needFailures.map((clip) => async () => call("PUT", PUBLISH_PATHS.failure(begun.publishId, clip), {
|
|
32
|
+
body: await files.failure(clip),
|
|
33
|
+
type: "image/png",
|
|
34
|
+
})),
|
|
35
|
+
];
|
|
36
|
+
options.log?.(` uploading ${begun.needStates.length} new state(s) of ` +
|
|
37
|
+
`${begin.clips.reduce((n, c) => n + (c.recording?.states.length ?? 0), 0)}`);
|
|
38
|
+
await pool(uploads, options.concurrency ?? 6);
|
|
39
|
+
const committed = (await call("POST", PUBLISH_PATHS.commit(begun.publishId), {
|
|
40
|
+
body: "{}",
|
|
41
|
+
type: "application/json",
|
|
42
|
+
}));
|
|
43
|
+
return { begun, committed };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* One request, retried on what a retry can fix: a network error, a 429 or a
|
|
47
|
+
* 5xx. Anything else is the service's considered answer, and its sentence is
|
|
48
|
+
* passed on unchanged, because it names the limit or the problem better than
|
|
49
|
+
* anything the client could add.
|
|
50
|
+
*/
|
|
51
|
+
function caller(options) {
|
|
52
|
+
const doFetch = options.fetch ?? fetch;
|
|
53
|
+
const backoff = options.backoffMs ?? [1000, 2000, 4000];
|
|
54
|
+
const origin = options.api.replace(/\/+$/, "");
|
|
55
|
+
return async (method, path, payload) => {
|
|
56
|
+
let lastError;
|
|
57
|
+
for (let attempt = 0; attempt <= backoff.length; attempt += 1) {
|
|
58
|
+
let response;
|
|
59
|
+
try {
|
|
60
|
+
response = await doFetch(`${origin}${path}`, {
|
|
61
|
+
method,
|
|
62
|
+
headers: {
|
|
63
|
+
authorization: `Bearer ${options.credential}`,
|
|
64
|
+
"content-type": payload.type,
|
|
65
|
+
},
|
|
66
|
+
// A copy for a byte body: fetch wants an ArrayBuffer-backed view,
|
|
67
|
+
// and a Buffer read from disk may sit in a shared pool.
|
|
68
|
+
body: typeof payload.body === "string"
|
|
69
|
+
? payload.body
|
|
70
|
+
: new Uint8Array(payload.body),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
lastError = error;
|
|
75
|
+
if (attempt < backoff.length)
|
|
76
|
+
await sleep(backoff[attempt]);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (response.ok) {
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
return text ? JSON.parse(text) : null;
|
|
82
|
+
}
|
|
83
|
+
if (response.status === 429 || response.status >= 500) {
|
|
84
|
+
lastError = new Error(`${method} ${path}: ${response.status} ${await response.text()}`);
|
|
85
|
+
if (attempt < backoff.length)
|
|
86
|
+
await sleep(backoff[attempt]);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const body = await response.text();
|
|
90
|
+
let parsed = null;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(body);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Not the service's JSON: a proxy or a misconfigured API address.
|
|
96
|
+
}
|
|
97
|
+
throw new PublishRefused(parsed?.error ??
|
|
98
|
+
`${method} ${path} answered ${response.status}: ${body.slice(0, 300)}`, response.status, parsed?.code ?? "http");
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`version.cam did not answer after ${backoff.length + 1} tries: ${lastError?.message ?? lastError}`);
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Run tasks with at most `size` in flight; the first failure fails the lot. */
|
|
104
|
+
async function pool(tasks, size) {
|
|
105
|
+
let next = 0;
|
|
106
|
+
const workers = Array.from({ length: Math.min(size, tasks.length) }, async () => {
|
|
107
|
+
while (next < tasks.length) {
|
|
108
|
+
const task = tasks[next];
|
|
109
|
+
next += 1;
|
|
110
|
+
await task();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
await Promise.all(workers);
|
|
114
|
+
}
|
|
115
|
+
function sleep(ms) {
|
|
116
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/publish/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `versioncam/publish`: what the version.cam service imports.
|
|
3
|
+
*
|
|
4
|
+
* Nothing reachable from here may import Node, Playwright or ffmpeg: the
|
|
5
|
+
* service runs on Cloudflare Workers, and one stray import of the driver
|
|
6
|
+
* would pull a browser into its bundle. The recorder's own publish code
|
|
7
|
+
* (manifests, the client) lives beside this file and is not re-exported.
|
|
8
|
+
*/
|
|
9
|
+
export * from "./protocol.js";
|
|
10
|
+
export { validateTimeline } from "../core/timeline.js";
|
|
11
|
+
export type { Timeline, Step, Rect, Mark, Caption } from "../core/timeline.js";
|
|
12
|
+
export type { Motion, Presentation } from "../config.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `versioncam/publish`: what the version.cam service imports.
|
|
3
|
+
*
|
|
4
|
+
* Nothing reachable from here may import Node, Playwright or ffmpeg: the
|
|
5
|
+
* service runs on Cloudflare Workers, and one stray import of the driver
|
|
6
|
+
* would pull a browser into its bundle. The recorder's own publish code
|
|
7
|
+
* (manifests, the client) lives beside this file and is not re-exported.
|
|
8
|
+
*/
|
|
9
|
+
export * from "./protocol.js";
|
|
10
|
+
export { validateTimeline } from "../core/timeline.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/publish/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ResolvedConfig } from "../config.js";
|
|
2
|
+
import { type ClipManifest, type PublishedRenderConfig } from "./protocol.js";
|
|
3
|
+
/**
|
|
4
|
+
* A recording directory, read into what a publish says about it.
|
|
5
|
+
*
|
|
6
|
+
* Everything the service is told is computed here from the files, never from
|
|
7
|
+
* the run that made them, so `publish` can run in a later CI step (or a later
|
|
8
|
+
* job) than the recording did and say exactly the same thing.
|
|
9
|
+
*/
|
|
10
|
+
export declare function sha256Hex(bytes: Uint8Array): string;
|
|
11
|
+
/**
|
|
12
|
+
* A PNG's size from its header. The signature is eight bytes and the first
|
|
13
|
+
* chunk is always IHDR, whose data starts with the width and height as
|
|
14
|
+
* big-endian 32-bit integers, so no decoder is needed to tell the service
|
|
15
|
+
* what it is about to receive.
|
|
16
|
+
*/
|
|
17
|
+
export declare function pngSize(bytes: Uint8Array): {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
/** What the service renders with: the render page's config, and its clock. */
|
|
22
|
+
export declare function publishedRenderConfig(config: ResolvedConfig): PublishedRenderConfig;
|
|
23
|
+
/** The files a recording's manifest was computed from, to upload on request. */
|
|
24
|
+
export type RecordingFiles = {
|
|
25
|
+
timeline: Uint8Array;
|
|
26
|
+
targets: Uint8Array | null;
|
|
27
|
+
/** By hash, for the states the service asks for. */
|
|
28
|
+
states: Map<string, string>;
|
|
29
|
+
failure: Uint8Array | null;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* One clip's manifest, from its recording directory.
|
|
33
|
+
*
|
|
34
|
+
* A directory holding `failure.json` failed its last run: the recorder
|
|
35
|
+
* clears a clip's directory before recording it, so a failure there is the
|
|
36
|
+
* latest word on the clip, and what it recorded before failing is not a
|
|
37
|
+
* recording anyone should see. Otherwise `timeline.json` is a complete
|
|
38
|
+
* recording that passed. With neither, the clip was never recorded here.
|
|
39
|
+
*/
|
|
40
|
+
export declare function readClip(dir: string, clip: {
|
|
41
|
+
id: string;
|
|
42
|
+
title: string;
|
|
43
|
+
}, renderConfig: PublishedRenderConfig): {
|
|
44
|
+
manifest: ClipManifest;
|
|
45
|
+
files: RecordingFiles;
|
|
46
|
+
} | null;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { renderConfigOf } from "../config.js";
|
|
5
|
+
import { FAILURE_JSON, FAILURE_PNG, TARGETS_FILE, } from "../driver/reports.js";
|
|
6
|
+
import { contentOfTimeline, } from "./protocol.js";
|
|
7
|
+
/**
|
|
8
|
+
* A recording directory, read into what a publish says about it.
|
|
9
|
+
*
|
|
10
|
+
* Everything the service is told is computed here from the files, never from
|
|
11
|
+
* the run that made them, so `publish` can run in a later CI step (or a later
|
|
12
|
+
* job) than the recording did and say exactly the same thing.
|
|
13
|
+
*/
|
|
14
|
+
export function sha256Hex(bytes) {
|
|
15
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A PNG's size from its header. The signature is eight bytes and the first
|
|
19
|
+
* chunk is always IHDR, whose data starts with the width and height as
|
|
20
|
+
* big-endian 32-bit integers, so no decoder is needed to tell the service
|
|
21
|
+
* what it is about to receive.
|
|
22
|
+
*/
|
|
23
|
+
export function pngSize(bytes) {
|
|
24
|
+
const view = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
25
|
+
const signature = "89504e470d0a1a0a";
|
|
26
|
+
if (view.length < 24 || view.subarray(0, 8).toString("hex") !== signature) {
|
|
27
|
+
throw new Error("not a PNG");
|
|
28
|
+
}
|
|
29
|
+
if (view.subarray(12, 16).toString("latin1") !== "IHDR") {
|
|
30
|
+
throw new Error("a PNG whose first chunk is not IHDR");
|
|
31
|
+
}
|
|
32
|
+
return { width: view.readUInt32BE(16), height: view.readUInt32BE(20) };
|
|
33
|
+
}
|
|
34
|
+
/** What the service renders with: the render page's config, and its clock. */
|
|
35
|
+
export function publishedRenderConfig(config) {
|
|
36
|
+
const { scenes: _scenes, ...render } = renderConfigOf(config);
|
|
37
|
+
return {
|
|
38
|
+
...render,
|
|
39
|
+
clock: {
|
|
40
|
+
timezoneId: config.clock.timezoneId,
|
|
41
|
+
locale: config.clock.locale,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* One clip's manifest, from its recording directory.
|
|
47
|
+
*
|
|
48
|
+
* A directory holding `failure.json` failed its last run: the recorder
|
|
49
|
+
* clears a clip's directory before recording it, so a failure there is the
|
|
50
|
+
* latest word on the clip, and what it recorded before failing is not a
|
|
51
|
+
* recording anyone should see. Otherwise `timeline.json` is a complete
|
|
52
|
+
* recording that passed. With neither, the clip was never recorded here.
|
|
53
|
+
*/
|
|
54
|
+
export function readClip(dir, clip, renderConfig) {
|
|
55
|
+
const failureJson = join(dir, FAILURE_JSON);
|
|
56
|
+
const timelineFile = join(dir, "timeline.json");
|
|
57
|
+
if (existsSync(failureJson)) {
|
|
58
|
+
const failure = JSON.parse(readFileSync(failureJson, "utf8"));
|
|
59
|
+
const png = join(dir, FAILURE_PNG);
|
|
60
|
+
const picture = existsSync(png) ? readFileSync(png) : null;
|
|
61
|
+
const check = {
|
|
62
|
+
status: "fail",
|
|
63
|
+
message: failure.message,
|
|
64
|
+
step: failure.step ?? null,
|
|
65
|
+
frame: failure.frame,
|
|
66
|
+
t: failure.t,
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
manifest: {
|
|
70
|
+
id: clip.id,
|
|
71
|
+
title: clip.title,
|
|
72
|
+
check,
|
|
73
|
+
recording: null,
|
|
74
|
+
failureHash: picture ? sha256Hex(picture) : null,
|
|
75
|
+
},
|
|
76
|
+
files: {
|
|
77
|
+
timeline: new Uint8Array(),
|
|
78
|
+
targets: null,
|
|
79
|
+
states: new Map(),
|
|
80
|
+
failure: picture,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (!existsSync(timelineFile))
|
|
85
|
+
return null;
|
|
86
|
+
const timelineBytes = readFileSync(timelineFile);
|
|
87
|
+
const timeline = JSON.parse(timelineBytes.toString("utf8"));
|
|
88
|
+
const targetsFile = join(dir, TARGETS_FILE);
|
|
89
|
+
const targets = existsSync(targetsFile) ? readFileSync(targetsFile) : null;
|
|
90
|
+
const statePaths = new Map();
|
|
91
|
+
const states = timeline.states.map((name) => {
|
|
92
|
+
const path = join(dir, "states", name);
|
|
93
|
+
const bytes = readFileSync(path);
|
|
94
|
+
const hash = sha256Hex(bytes);
|
|
95
|
+
statePaths.set(hash, path);
|
|
96
|
+
return { hash, bytes: bytes.length, ...pngSize(bytes) };
|
|
97
|
+
});
|
|
98
|
+
const targetsHash = targets ? sha256Hex(targets) : null;
|
|
99
|
+
const contentHash = sha256Hex(Buffer.from(JSON.stringify({
|
|
100
|
+
timeline: contentOfTimeline(timeline),
|
|
101
|
+
states: states.map((s) => s.hash),
|
|
102
|
+
targets: targetsHash,
|
|
103
|
+
renderConfig,
|
|
104
|
+
})));
|
|
105
|
+
const recording = {
|
|
106
|
+
contentHash,
|
|
107
|
+
timelineHash: sha256Hex(timelineBytes),
|
|
108
|
+
timelineBytes: timelineBytes.length,
|
|
109
|
+
targetsHash,
|
|
110
|
+
draft: timeline.meta.draft === true,
|
|
111
|
+
fps: timeline.fps,
|
|
112
|
+
durationFrames: timeline.durationFrames,
|
|
113
|
+
viewport: timeline.viewport,
|
|
114
|
+
states,
|
|
115
|
+
renderConfig,
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
manifest: {
|
|
119
|
+
id: clip.id,
|
|
120
|
+
title: clip.title || timeline.meta.title,
|
|
121
|
+
check: { status: "pass" },
|
|
122
|
+
recording,
|
|
123
|
+
failureHash: null,
|
|
124
|
+
},
|
|
125
|
+
files: {
|
|
126
|
+
timeline: timelineBytes,
|
|
127
|
+
targets,
|
|
128
|
+
states: statePaths,
|
|
129
|
+
failure: null,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/publish/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,GAEb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,iBAAiB,GAMlB,MAAM,eAAe,CAAC;AAEvB;;;;;;GAMG;AAEH,MAAM,UAAU,SAAS,CAAC,KAAiB;IACzC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,KAAiB;IACvC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3E,MAAM,SAAS,GAAG,kBAAkB,CAAC;IACrC,IAAI,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;QAC1E,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,MAAM,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC;AACzE,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,qBAAqB,CACnC,MAAsB;IAEtB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAC9D,OAAO;QACL,GAAG,MAAM;QACT,KAAK,EAAE;YACL,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU;YACnC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;SAC5B;KACF,CAAC;AACJ,CAAC;AAWD;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CACtB,GAAW,EACX,IAAmC,EACnC,YAAmC;IAEnC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAEhD,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAY,CAAC;QACzE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACnC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3D,MAAM,KAAK,GAAgB;YACzB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,IAAI;YAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,CAAC,EAAE,OAAO,CAAC,CAAC;SACb,CAAC;QACF,OAAO;YACL,QAAQ,EAAE;gBACR,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,KAAK;gBACL,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI;aACjD;YACD,KAAK,EAAE;gBACL,QAAQ,EAAE,IAAI,UAAU,EAAE;gBAC1B,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,IAAI,GAAG,EAAE;gBACjB,OAAO,EAAE,OAAO;aACjB;SACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,MAAM,aAAa,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAa,CAAC;IACxE,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE3E,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,MAAM,MAAM,GAAe,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACtD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9B,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,MAAM,WAAW,GAAG,SAAS,CAC3B,MAAM,CAAC,IAAI,CACT,IAAI,CAAC,SAAS,CAAC;QACb,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC;QACrC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACjC,OAAO,EAAE,WAAW;QACpB,YAAY;KACb,CAAC,CACH,CACF,CAAC;IAEF,MAAM,SAAS,GAAsB;QACnC,WAAW;QACX,YAAY,EAAE,SAAS,CAAC,aAAa,CAAC;QACtC,aAAa,EAAE,aAAa,CAAC,MAAM;QACnC,WAAW;QACX,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI;QACnC,GAAG,EAAE,QAAQ,CAAC,GAAG;QACjB,cAAc,EAAE,QAAQ,CAAC,cAAc;QACvC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,MAAM;QACN,YAAY;KACb,CAAC;IAEF,OAAO;QACL,QAAQ,EAAE;YACR,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK;YACxC,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;YACzB,SAAS;YACT,WAAW,EAAE,IAAI;SAClB;QACD,KAAK,EAAE;YACL,QAAQ,EAAE,aAAa;YACvB,OAAO;YACP,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,IAAI;SACd;KACF,CAAC;AACJ,CAAC"}
|