versioncam 0.5.3 → 0.7.1

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +108 -6
  3. package/dist/cli/commands/check.js +25 -0
  4. package/dist/cli/commands/check.js.map +1 -1
  5. package/dist/cli/commands/doctor.js +54 -1
  6. package/dist/cli/commands/doctor.js.map +1 -1
  7. package/dist/cli/commands/preview.js +4 -2
  8. package/dist/cli/commands/preview.js.map +1 -1
  9. package/dist/cli/commands/record.d.ts +6 -0
  10. package/dist/cli/commands/record.js +24 -3
  11. package/dist/cli/commands/record.js.map +1 -1
  12. package/dist/cli/commands/stability.js +10 -3
  13. package/dist/cli/commands/stability.js.map +1 -1
  14. package/dist/cli/usage.d.ts +1 -1
  15. package/dist/cli/usage.js +2 -0
  16. package/dist/cli/usage.js.map +1 -1
  17. package/dist/config.d.ts +45 -4
  18. package/dist/config.js +134 -8
  19. package/dist/config.js.map +1 -1
  20. package/dist/core/cursor.d.ts +22 -0
  21. package/dist/core/cursor.js +6 -0
  22. package/dist/core/cursor.js.map +1 -1
  23. package/dist/core/motion-defaults.d.ts +8 -0
  24. package/dist/core/motion-defaults.js +8 -0
  25. package/dist/core/motion-defaults.js.map +1 -1
  26. package/dist/core/timeline.d.ts +24 -0
  27. package/dist/core/timeline.js.map +1 -1
  28. package/dist/driver/clip.d.ts +6 -0
  29. package/dist/driver/clip.js +33 -2
  30. package/dist/driver/clip.js.map +1 -1
  31. package/dist/driver/file-window.d.ts +66 -0
  32. package/dist/driver/file-window.js +245 -0
  33. package/dist/driver/file-window.js.map +1 -0
  34. package/dist/driver/page-hooks.d.ts +23 -1
  35. package/dist/driver/page-hooks.js +36 -1
  36. package/dist/driver/page-hooks.js.map +1 -1
  37. package/dist/driver/replay.d.ts +263 -0
  38. package/dist/driver/replay.js +558 -0
  39. package/dist/driver/replay.js.map +1 -0
  40. package/dist/driver/session.d.ts +70 -4
  41. package/dist/driver/session.js +267 -29
  42. package/dist/driver/session.js.map +1 -1
  43. package/dist/driver/settle.js +6 -0
  44. package/dist/driver/settle.js.map +1 -1
  45. package/dist/index.d.ts +4 -2
  46. package/dist/index.js.map +1 -1
  47. package/dist/page/assets/index-CnRDAiUz.js +2 -0
  48. package/dist/page/index.html +1 -1
  49. package/dist/publish/protocol.d.ts +12 -1
  50. package/dist/publish/protocol.js.map +1 -1
  51. package/dist/render/render.d.ts +7 -1
  52. package/dist/render/render.js +51 -17
  53. package/dist/render/render.js.map +1 -1
  54. package/dist/render/sequence.d.ts +10 -5
  55. package/dist/render/sequence.js +44 -29
  56. package/dist/render/sequence.js.map +1 -1
  57. package/dsl.md +68 -1
  58. package/package.json +1 -1
  59. package/plugin/.claude-plugin/plugin.json +1 -1
  60. package/plugin/skills/versioncam/authoring.md +15 -1
  61. package/plugin/skills/versioncam/onboarding.md +9 -3
  62. package/dist/page/assets/index-BtUbUEdN.js +0 -2
package/dist/index.d.ts CHANGED
@@ -5,7 +5,8 @@ export type { AppHandle } from "./app-server.js";
5
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
- export type { RecordingSession, SessionOptions } from "./driver/session.js";
8
+ export type { DragOptions, FileWindow, Modifier, RecordingSession, ScrollOptions, ScrollWhere, SessionOptions, } from "./driver/session.js";
9
+ export type { FileWindowOptions, StagedFile } from "./driver/file-window.js";
9
10
  export { createGate } from "./driver/gate.js";
10
11
  export type { Gate } from "./driver/gate.js";
11
12
  export type { SettleOptions, SettleReport } from "./driver/settle.js";
@@ -17,7 +18,8 @@ export type { LoopPlanner, LoopOptions } from "./core/loop.js";
17
18
  export { sseBody } from "./core/sse.js";
18
19
  export type { SseFixtureEvent } from "./core/sse.js";
19
20
  export { MOTION } from "./core/motion-defaults.js";
20
- export type { Caption, CameraKey, Highlight, HighlightStyle, Mark, PointerAction, Rect, Timeline, Vec2, } from "./core/timeline.js";
21
+ export type { ReplayConfig, ReplayMode, ReplayPattern, } from "./driver/replay.js";
22
+ export type { Caption, CameraKey, Highlight, HighlightStyle, Mark, PointerAction, Rect, Scroll, Timeline, Vec2, } from "./core/timeline.js";
21
23
  export type { Scene, Size } from "./scene.js";
22
24
  export { loadClips, loadConfig, loadSceneIds, bundleScenes } from "./loader.js";
23
25
  export { contactSheet, renderFrame, renderRecording, renderScene, withBrowser, readTimeline, } from "./render/render.js";
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;AAiBxE,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"}
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;AAiBxE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAGrD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAkBnE,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;AAqBnD,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"}
@@ -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}var c={style:`light`,ripple:!0};function l(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 u(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 d(e,t){return{x:e.x*t,y:e.y*t,w:e.w*t,h:e.h*t}}function f(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 p(e,n,r){return r<=0?1:t((e-n+1)/r)}function m(e,t,n){let r=e.frames[t],i=null;for(let a of e.cuts)t>a&&t<=a+n&&e.frames[a]!==r&&(i===null||a>i)&&(i=a);return i===null?{current:r,previous:null,alpha:1}:{current:r,previous:e.frames[i],alpha:p(t,i+1,n)}}function h(e,n,r,i){return e<=n||e>=r?0:i<=0?1:Math.min(t((e-n)/i),t((r-e)/i))}function g(e,t){return e/t}function _(e,t,n){let r=n?.padding??0;return!n||r<=0?{x:0,y:0,w:e.width,h:e.height}:f(e,t,r)}function v(e){let t=e?.caption??`auto`;return t===`none`?`none`:t===`below`||t===`overlay`?t:(e?.padding??0)>0?`below`:`overlay`}function y(e,t){let n=e?.captionInset;return n?.right===void 0?{x:n?.left??40,align:`left`}:{x:t-n.right,align:`right`}}var b={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,ringWidth:3,ringRadius:10,spotlightDim:.55,spotlightTint:`6, 2, 16`},x={light:{fill:`#ffffff`,stroke:`rgba(0, 0, 0, 0.55)`,shadow:`rgba(0, 0, 0, 0.35)`},dark:{fill:`#000000`,stroke:`rgba(255, 255, 255, 0.9)`,shadow:`rgba(0, 0, 0, 0.3)`}};function ee(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 S(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 te(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 ne(e,t,n,r,i,a,o){let s=d(i,t.viewport.dpr),{current:c,previous:l,alpha:u}=m(t,r,o.motion.crossfadeFrames),f=o.theme.presentation?.frame??!1;e.save(),f&&(S(e,a,b.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&&p(l,1),p(c,l===null?1:u),e.globalAlpha=1,e.restore()}function C(e,t){e.save(),e.shadowColor=b.shadow,e.shadowBlur=b.shadowBlur,e.shadowOffsetY=b.shadowOffsetY,e.fillStyle=`rgba(0,0,0,1)`,S(e,t,b.radius),e.fill(),e.restore()}function w(e,t){e.save(),e.strokeStyle=b.hairline,e.lineWidth=1,S(e,t,b.radius),e.stroke(),e.restore()}function T(e,t){return{x:e.x-t,y:e.y-t,w:e.w+t*2,h:e.h+t*2}}function E(e,t,n,r,i,a){let o=a.theme.presentation?.frame??!1;for(let s of t.highlights){let t=h(n,s.t0,s.t1,.25);if(t<=0)continue;let c=u(s.rect,r,i);e.save(),e.globalAlpha=t,s.style===`spotlight`?(e.save(),S(e,i,o?b.radius:0),e.clip(),e.fillStyle=`rgba(${b.spotlightTint}, ${b.spotlightDim})`,e.fillRect(i.x,i.y,i.w,i.h),e.globalCompositeOperation=`destination-out`,S(e,T(c,8),b.ringRadius),e.fill(),e.restore()):(e.strokeStyle=a.theme.accent,e.lineWidth=b.ringWidth,e.shadowColor=ee(a.theme.accent,.35),e.shadowBlur=18,S(e,T(c,6),b.ringRadius),e.stroke()),e.restore()}}function D(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 re(e,t,n,r,i){for(let a of t){let t=a.age/(i.motion.rippleMs/1e3),[o,s]=l(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 ie(e,t,n,r,i){let a=r*n;e.save(),e.translate(t[0],t[1]),e.scale(a,a),e.shadowColor=i.shadow,e.shadowBlur=6/a,e.shadowOffsetY=2/a,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=i.fill,e.fill(),e.shadowColor=`transparent`,e.strokeStyle=i.stroke,e.lineWidth=1/a,e.stroke(),e.restore()}function O(e,t,n,r,i,a){let o=v(a.theme.presentation);if(o!==`none`)for(let s of t.captions){let t=h(n,s.t0,s.t1,a.motion.captionFadeS);if(t<=0)continue;if(e.save(),e.globalAlpha=t,o===`overlay`){let t=b.captionScrimHeight,n=e.createLinearGradient(0,i.height-t,0,i.height);n.addColorStop(0,`rgba(0, 0, 0, 0)`),n.addColorStop(1,b.captionScrim),e.fillStyle=n,e.fillRect(0,i.height-t,i.width,t)}e.font=b.captionFont,e.fillStyle=b.captionColor,e.textBaseline=`middle`,e.shadowColor=b.captionShadow,e.shadowBlur=8;let c=r.x,l=(r.y+r.h+i.height)/2;if(o===`overlay`){let t=y(a.theme.presentation,i.width);e.textAlign=t.align,c=t.x,l=i.height-b.captionScrimHeight/2+4}e.fillText(s.text,c,l),e.restore()}}function k(e,t,n,r,i,o){let u=g(r,t.fps),d=a(t.camera,u),f=o.theme.presentation,p=_(i,t.viewport,f);f?.backdrop&&te(e,i,f.backdrop),f?.frame&&C(e,p),ne(e,t,n,r,d,p,o),f?.frame&&w(e,p),E(e,t,u,d,p,o);let m=o.theme.cursor??c,{scale:h,ripples:v}=D(t,u,o);m.ripple&&re(e,v,d,p,o),ie(e,l(s(t.cursor,u),d,p),h,o.theme.cursorScale,x[m.style]),O(e,t,u,p,i,o)}var A="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 j(e){return e==null?String(e):Array.isArray(e)?`an array`:`a ${typeof e}`}function M(e){return typeof e==`string`?JSON.stringify(e):String(e)}function N(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function ae(e,t){if(!N(t))return`"${e}" is ${j(t)}, not a scene`;let{draw:n,durationFrames:r,fps:i}=t;return typeof n==`function`?!Number.isInteger(r)||r<1?`"${e}" has durationFrames ${M(r)}, where a whole number of frames, 1 or more, belongs`:typeof i!=`number`||!Number.isFinite(i)||i<=0?`"${e}" has fps ${M(i)}, where a number above 0 belongs`:null:`"${e}" has no draw(ctx, frame, canvas)`}function P(e){if(!N(e))return j(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=ae(t,n);if(e)return`a record in which ${e}`}return null}function oe(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(`, `)}.`:``),A],i=n.filter(t=>P(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=P(e.default);return n?{error:`${t} default-exports ${n}.\n${A}`}:{scenes:e.default}}function F(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 I=`modulepreload`,L=function(e,t){return new URL(e,t).href},R={},z=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=L(t,n),t=s(t),t in R)return;R[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`:I,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)})},B=new URLSearchParams(location.search),V=B.has(`recording`),H=B.get(`scene`),U=B.get(`headless`)===`1`;U&&document.body.classList.add(`headless`);var W=document.getElementById(`stage`),G=document.getElementById(`scrub`),K=document.getElementById(`status`),q=document.getElementById(`play`),se=document.getElementById(`error`);function J(e){throw se.textContent=e,Error(e)}async function Y(e,t){let n=await fetch(e);return n.ok||J(`Could not load ${t} (${e}): ${n.status}`),await n.json()}async function ce(e){let t=await fetch(e);return t.ok||J(`Could not load ${e}: ${t.status}`),createImageBitmap(await t.blob())}function X(e){W.width=e.output.width,W.height=e.output.height,U?(W.style.width=`${e.output.width}px`,W.style.height=`${e.output.height}px`):(W.style.width=`min(100%, 1280px)`,W.style.height=`auto`);let t=W.getContext(`2d`,{alpha:!1});return t||J(`No 2D context`),t.imageSmoothingEnabled=!0,t.imageSmoothingQuality=`high`,t}function Z(e,t,n,r){let i=!1;q.addEventListener(`click`,()=>{i=!i,q.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 Q(e,t,n,r){e.durationFrames=t,e.fps=n,e.setFrame=e=>new Promise(t=>{r(e),requestAnimationFrame(()=>requestAnimationFrame(()=>t()))})}async function le(e,t){let n=await Y(`/recording/timeline.json`,`the recording`),r=X(t),i={images:await Promise.all(n.states.map(e=>ce(`/recording/states/${e}`)))};await document.fonts.ready;let a=-1,o=e=>{let o=Math.max(0,Math.min(n.durationFrames-1,e));k(r,n,i,o,W,t),a=o,U||(G.value=String(o),K.textContent=`${o+1} / ${n.durationFrames} · ${(o/n.fps).toFixed(2)}s · state ${n.frames[o]}`)};G.max=String(n.durationFrames-1),G.addEventListener(`input`,()=>o(Number(G.value))),Z(n.fps,n.durationFrames,()=>a,o),o(Number(B.get(`frame`)??0)),Q(e,n.durationFrames,n.fps,o)}async function ue(e,t,n){t.scenes===null&&J(F([n],[],null));let r=oe(await z(()=>import(`/scenes.js`),[],import.meta.url),t.scenes);`error`in r&&J(r.error);let i=r.scenes[n];i||J(F([n],Object.keys(r.scenes),t.scenes));let a=X(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,W),o=t,U||(G.value=String(t),K.textContent=`${t+1} / ${i.durationFrames} · ${(t/i.fps).toFixed(2)}s`)};G.max=String(i.durationFrames-1),G.addEventListener(`input`,()=>s(Number(G.value))),Z(i.fps,i.durationFrames,()=>o,s),s(Number(B.get(`frame`)??0)),Q(e,i.durationFrames,i.fps,s)}async function de(e){let t=await Y(`/render-config.json`,`the render config`);if(H)return ue(e,t,H);if(V)return le(e,t);J(`Pass ?recording or ?scene=<id>.`)}var $={ready:Promise.resolve(),setFrame:async()=>{},durationFrames:0,fps:60};window.__render=$,$.ready=de($);
@@ -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-BtUbUEdN.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-CnRDAiUz.js"></script>
8
8
  </head>
9
9
  <body>
10
10
  <canvas id="stage"></canvas>
@@ -71,10 +71,21 @@ export type PublishedRenderConfig = {
71
71
  theme: {
72
72
  accent: string;
73
73
  cursorScale: number;
74
+ /**
75
+ * The config's `theme.cursor`, from 0.6.0, and only when it is not the
76
+ * default; a renderer draws the default without it.
77
+ */
78
+ cursor?: {
79
+ style: "light" | "dark";
80
+ ripple: boolean;
81
+ };
74
82
  /** The config's `theme.presentation`, or null when it is off. */
75
83
  presentation: Record<string, unknown> | null;
76
84
  };
77
- /** The config's motion, every default filled in. */
85
+ /**
86
+ * The config's motion, every default filled in, but for the settings
87
+ * added since 0.5.3, which are sent only when they are not the default.
88
+ */
78
89
  motion: Record<string, unknown>;
79
90
  clock: {
80
91
  timezoneId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/publish/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,uDAAuD;AACvD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAElC,oEAAoE;AACpE,MAAM,CAAC,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAErD;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,gBAAgB,EAAE,GAAG;IACrB,UAAU,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC3B,aAAa,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC9B,YAAY,EAAE,GAAG,GAAG,IAAI;IACxB,YAAY,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC7B,gBAAgB,EAAE,EAAE;CACZ,CAAC;AAoJX,2CAA2C;AAC3C,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,KAAK,EAAE,mBAAmB;IAC1B,KAAK,EAAE,CAAC,EAAU,EAAE,IAAY,EAAE,EAAE,CAAC,eAAe,EAAE,UAAU,IAAI,EAAE;IACtE,QAAQ,EAAE,CAAC,EAAU,EAAE,IAAY,EAAE,EAAE,CACrC,eAAe,EAAE,aAAa,kBAAkB,CAAC,IAAI,CAAC,EAAE;IAC1D,OAAO,EAAE,CAAC,EAAU,EAAE,IAAY,EAAE,EAAE,CACpC,eAAe,EAAE,YAAY,kBAAkB,CAAC,IAAI,CAAC,EAAE;IACzD,OAAO,EAAE,CAAC,EAAU,EAAE,IAAY,EAAE,EAAE,CACpC,eAAe,EAAE,YAAY,kBAAkB,CAAC,IAAI,CAAC,EAAE;IACzD,MAAM,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,eAAe,EAAE,SAAS;CAC1C,CAAC;AAEX;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAEjC;IACC,MAAM,EACJ,UAAU,EAAE,WAAW,EACvB,SAAS,EAAE,UAAU,EACrB,GAAG,IAAI,EACR,GAAG,QAAQ,CAAC,IAAI,CAAC;IAClB,OAAO,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC/B,CAAC"}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/publish/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,uDAAuD;AACvD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAElC,oEAAoE;AACpE,MAAM,CAAC,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAErD;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,gBAAgB,EAAE,GAAG;IACrB,UAAU,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC3B,aAAa,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC9B,YAAY,EAAE,GAAG,GAAG,IAAI;IACxB,YAAY,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC7B,gBAAgB,EAAE,EAAE;CACZ,CAAC;AA4JX,2CAA2C;AAC3C,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,KAAK,EAAE,mBAAmB;IAC1B,KAAK,EAAE,CAAC,EAAU,EAAE,IAAY,EAAE,EAAE,CAAC,eAAe,EAAE,UAAU,IAAI,EAAE;IACtE,QAAQ,EAAE,CAAC,EAAU,EAAE,IAAY,EAAE,EAAE,CACrC,eAAe,EAAE,aAAa,kBAAkB,CAAC,IAAI,CAAC,EAAE;IAC1D,OAAO,EAAE,CAAC,EAAU,EAAE,IAAY,EAAE,EAAE,CACpC,eAAe,EAAE,YAAY,kBAAkB,CAAC,IAAI,CAAC,EAAE;IACzD,OAAO,EAAE,CAAC,EAAU,EAAE,IAAY,EAAE,EAAE,CACpC,eAAe,EAAE,YAAY,kBAAkB,CAAC,IAAI,CAAC,EAAE;IACzD,MAAM,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,eAAe,EAAE,SAAS;CAC1C,CAAC;AAEX;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAEjC;IACC,MAAM,EACJ,UAAU,EAAE,WAAW,EACvB,SAAS,EAAE,UAAU,EACrB,GAAG,IAAI,EACR,GAAG,QAAQ,CAAC,IAAI,CAAC;IAClB,OAAO,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC/B,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import type { Browser, Page } from "playwright";
2
- import type { ResolvedConfig } from "../config.js";
2
+ import type { RenderConfig, ResolvedConfig } from "../config.js";
3
3
  import type { Timeline } from "../core/timeline.js";
4
4
  import type { EncodeResult } from "./encode.js";
5
5
  export type RenderTarget = {
@@ -10,6 +10,8 @@ export type RenderTarget = {
10
10
  kind: "scene";
11
11
  id: string;
12
12
  };
13
+ /** `renderConfigWith` for a target, for a page that is not opened here. */
14
+ export declare function renderConfigFor(config: ResolvedConfig, target: RenderTarget): Promise<RenderConfig>;
13
15
  /**
14
16
  * Open the render page at the exact output size, at DPR 1.
15
17
  *
@@ -17,11 +19,15 @@ export type RenderTarget = {
17
19
  * device scale factor of 2 would make the screenshot twice as wide and then
18
20
  * need downscaling — a second resample on top of the one `drawImage` already
19
21
  * did.
22
+ *
23
+ * The recording's timeline is read here, once, for the size, and handed back
24
+ * for the sheet, which needs its cuts.
20
25
  */
21
26
  export declare function openRenderPage(browser: Browser, config: ResolvedConfig, target: RenderTarget): Promise<{
22
27
  page: Page;
23
28
  durationFrames: number;
24
29
  fps: number;
30
+ timeline: Timeline | null;
25
31
  }>;
26
32
  export type RenderOutcome = EncodeResult & {
27
33
  id: string;
@@ -2,11 +2,41 @@ import { existsSync } from "node:fs";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { launchBrowser } from "../driver/launch.js";
5
- import { renderConfigOf } from "../config.js";
5
+ import { outputSize, renderConfigOf } from "../config.js";
6
6
  import { bundleScenes } from "../loader.js";
7
7
  import { contactSheet as encodeContactSheet, encodeFromPage, } from "./encode.js";
8
8
  import { evenFrames } from "./sampling.js";
9
9
  import { distPageDir, installRenderRoutes, RENDER_ORIGIN } from "./serve.js";
10
+ /** A recording's timeline, read from its directory, wherever that is. */
11
+ async function timelineAt(dir) {
12
+ return JSON.parse(await readFile(join(dir, "timeline.json"), "utf8"));
13
+ }
14
+ /**
15
+ * The render config a target is drawn with: the config's, at the target's
16
+ * own size.
17
+ *
18
+ * A recording renders at the shape it was recorded at, which is the clip's
19
+ * own viewport when it sets one. `config.output` is the config's viewport
20
+ * at `output.width`, and a phone clip recorded at 390x844 drawn on that
21
+ * canvas was the top of its page blown up to the width. A scene has no
22
+ * recording and draws at the config's size.
23
+ *
24
+ * `renderConfigOf` itself is left as it is: it is also what `versioncam
25
+ * publish` sends, and version.cam sizes each render from the recording's
26
+ * timeline by the same rule, so a publish keeps its bytes and its hash.
27
+ */
28
+ function renderConfigWith(config, timeline) {
29
+ return {
30
+ ...renderConfigOf(config),
31
+ output: timeline
32
+ ? outputSize(timeline.viewport, config.output.width)
33
+ : config.output,
34
+ };
35
+ }
36
+ /** `renderConfigWith` for a target, for a page that is not opened here. */
37
+ export async function renderConfigFor(config, target) {
38
+ return renderConfigWith(config, target.kind === "recording" ? await timelineAt(target.dir) : null);
39
+ }
10
40
  /**
11
41
  * Open the render page at the exact output size, at DPR 1.
12
42
  *
@@ -14,10 +44,15 @@ import { distPageDir, installRenderRoutes, RENDER_ORIGIN } from "./serve.js";
14
44
  * device scale factor of 2 would make the screenshot twice as wide and then
15
45
  * need downscaling — a second resample on top of the one `drawImage` already
16
46
  * did.
47
+ *
48
+ * The recording's timeline is read here, once, for the size, and handed back
49
+ * for the sheet, which needs its cuts.
17
50
  */
18
51
  export async function openRenderPage(browser, config, target) {
52
+ const timeline = target.kind === "recording" ? await timelineAt(target.dir) : null;
53
+ const renderConfig = renderConfigWith(config, timeline);
19
54
  const context = await browser.newContext({
20
- viewport: config.output,
55
+ viewport: renderConfig.output,
21
56
  deviceScaleFactor: 1,
22
57
  reducedMotion: "reduce",
23
58
  timezoneId: config.clock.timezoneId,
@@ -32,7 +67,7 @@ export async function openRenderPage(browser, config, target) {
32
67
  });
33
68
  await installRenderRoutes(page, {
34
69
  distDir: distPageDir(),
35
- renderConfig: renderConfigOf(config),
70
+ renderConfig,
36
71
  recordingDir: target.kind === "recording" ? target.dir : undefined,
37
72
  scenesBundle: (await bundleScenes(config)) ?? undefined,
38
73
  });
@@ -60,25 +95,25 @@ export async function openRenderPage(browser, config, target) {
60
95
  throw new Error(`The render page reported no frames for ${target.kind} "${target.id}".` +
61
96
  (problems.length ? `\n${problems.join("\n")}` : ""));
62
97
  }
63
- return { page, ...info };
98
+ return { page, ...info, timeline };
64
99
  }
65
100
  /**
66
101
  * The frames a sheet shows when nobody chose them.
67
102
  *
68
103
  * For a recording, evenly spread and moved past the renderer's dissolves,
69
- * which only the recording's own cuts can say where they are. Read from its
70
- * directory rather than by id, since a target can be a recording anywhere. A
71
- * scene draws every frame itself and never dissolves, so its sheet is the
72
- * plain even spread the encoder falls back on.
104
+ * which only the recording's own cuts can say where they are: the timeline
105
+ * `openRenderPage` read from its directory rather than by id, since a target
106
+ * can be a recording anywhere. A scene, which has none, draws every frame
107
+ * itself and never dissolves, so its sheet is the plain even spread the
108
+ * encoder falls back on.
73
109
  */
74
- async function sheetFrames(config, target, durationFrames) {
75
- if (target.kind !== "recording")
110
+ function sheetFrames(config, timeline, durationFrames) {
111
+ if (!timeline)
76
112
  return undefined;
77
- const timeline = JSON.parse(await readFile(join(target.dir, "timeline.json"), "utf8"));
78
113
  return evenFrames(durationFrames, undefined, config.motion.crossfadeFrames, timeline.cuts);
79
114
  }
80
115
  async function renderTarget(browser, config, target) {
81
- const { page, durationFrames, fps } = await openRenderPage(browser, config, target);
116
+ const { page, durationFrames, fps, timeline } = await openRenderPage(browser, config, target);
82
117
  try {
83
118
  const encoded = await encodeFromPage(page, {
84
119
  id: target.id,
@@ -90,7 +125,7 @@ async function renderTarget(browser, config, target) {
90
125
  const sheet = await encodeContactSheet(page, {
91
126
  id: target.id,
92
127
  durationFrames,
93
- frames: await sheetFrames(config, target, durationFrames),
128
+ frames: sheetFrames(config, timeline, durationFrames),
94
129
  outDir: config.outputDir,
95
130
  });
96
131
  return { ...encoded, id: target.id, durationFrames, fps, sheet };
@@ -110,12 +145,12 @@ export async function renderScene(browser, config, id) {
110
145
  return renderTarget(browser, config, { kind: "scene", id });
111
146
  }
112
147
  export async function contactSheet(browser, config, target, options = {}) {
113
- const { page, durationFrames } = await openRenderPage(browser, config, target);
148
+ const { page, durationFrames, timeline } = await openRenderPage(browser, config, target);
114
149
  try {
115
150
  return await encodeContactSheet(page, {
116
151
  id: target.id,
117
152
  durationFrames,
118
- frames: options.frames ?? (await sheetFrames(config, target, durationFrames)),
153
+ frames: options.frames ?? sheetFrames(config, timeline, durationFrames),
119
154
  outDir: config.outputDir,
120
155
  });
121
156
  }
@@ -147,8 +182,7 @@ export async function renderFrame(browser, config, target, frame) {
147
182
  }
148
183
  /** The timeline of a recording on disk, for reporting without a browser. */
149
184
  export async function readTimeline(config, id) {
150
- const file = join(config.recordingsDir, id, "timeline.json");
151
- return JSON.parse(await readFile(file, "utf8"));
185
+ return timelineAt(join(config.recordingsDir, id));
152
186
  }
153
187
  export async function withBrowser(fn) {
154
188
  const browser = await launchBrowser();
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/render/render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/render/render.ts"],"names":[],"mappings":"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"}
@@ -2,18 +2,23 @@
2
2
  * A rendered clip. `frameRate` is the fraction ffprobe gives, `60/1` or
3
3
  * `30000/1001`, kept as written rather than divided out: it is the rate the
4
4
  * filter may convert every clip to, the fastest clip included, and only the
5
- * exact rate leaves that clip's frames where they were.
5
+ * exact rate leaves that clip's frames where they were. `width` and
6
+ * `height` are the video's, which `xfade` needs to be the same for all.
6
7
  */
7
8
  export type SequenceInput = {
8
9
  id: string;
9
10
  path: string;
10
11
  duration: number;
11
12
  frameRate: string;
13
+ width: number;
14
+ height: number;
12
15
  };
13
- /** Duration of a video file, in seconds. Blocking; ffprobe is quick. */
14
- export declare function probeDuration(path: string): number;
15
- /** Frame rate of a video file, as the fraction ffprobe gives. Blocking too. */
16
- export declare function probeFrameRate(path: string): string;
16
+ /**
17
+ * What a sequence needs to know of a video file, from one ffprobe call:
18
+ * its duration in seconds, its frame rate as ffprobe gives it, and its
19
+ * size. Blocking; ffprobe is quick.
20
+ */
21
+ export declare function probeVideo(path: string): Pick<SequenceInput, "duration" | "frameRate" | "width" | "height">;
17
22
  /**
18
23
  * The `xfade` chain. Each transition's `offset` is measured from the start of
19
24
  * everything accumulated so far, and every crossfade shortens the total by its
@@ -16,47 +16,66 @@ import { ffmpegPath, ffprobeSync } from "./ffmpeg.js";
16
16
  * card per clip adds a sixth of the running time for nothing.
17
17
  */
18
18
  const TRANSITION_S = 0.4;
19
- /** Duration of a video file, in seconds. Blocking; ffprobe is quick. */
20
- export function probeDuration(path) {
19
+ /**
20
+ * What a sequence needs to know of a video file, from one ffprobe call:
21
+ * its duration in seconds, its frame rate as ffprobe gives it, and its
22
+ * size. Blocking; ffprobe is quick.
23
+ */
24
+ export function probeVideo(path) {
21
25
  const out = ffprobeSync([
22
26
  "-v",
23
27
  "error",
28
+ "-select_streams",
29
+ "v:0",
24
30
  "-show_entries",
25
- "format=duration",
31
+ "format=duration:stream=r_frame_rate,width,height",
26
32
  "-of",
27
- "default=nw=1:nk=1",
33
+ "json",
28
34
  path,
29
35
  ]);
30
- const seconds = Number.parseFloat(out.trim());
31
- if (!Number.isFinite(seconds)) {
36
+ const probed = JSON.parse(out);
37
+ const duration = Number.parseFloat(probed.format?.duration ?? "");
38
+ if (!Number.isFinite(duration)) {
32
39
  throw new Error(`Could not read a duration from ${path}`);
33
40
  }
34
- return seconds;
35
- }
36
- /** Frame rate of a video file, as the fraction ffprobe gives. Blocking too. */
37
- export function probeFrameRate(path) {
38
- const out = ffprobeSync([
39
- "-v",
40
- "error",
41
- "-select_streams",
42
- "v:0",
43
- "-show_entries",
44
- "stream=r_frame_rate",
45
- "-of",
46
- "default=nw=1:nk=1",
47
- path,
48
- ]).trim();
49
- const rate = rateOf(out);
41
+ const stream = probed.streams?.[0];
42
+ const frameRate = stream?.r_frame_rate ?? "";
43
+ const rate = rateOf(frameRate);
50
44
  if (!Number.isFinite(rate) || rate <= 0) {
51
45
  throw new Error(`Could not read a frame rate from ${path}`);
52
46
  }
53
- return out;
47
+ const { width, height } = stream ?? {};
48
+ if (!width || !height) {
49
+ throw new Error(`Could not read a size from ${path}`);
50
+ }
51
+ return { duration, frameRate, width, height };
54
52
  }
55
53
  /** A fraction as ffprobe prints one, `30000/1001`, as a number. */
56
54
  function rateOf(fraction) {
57
55
  const [num, den = "1"] = fraction.split("/");
58
56
  return Number(num) / Number(den);
59
57
  }
58
+ /**
59
+ * `xfade` joins clips of one size only, and when they are not, the error is
60
+ * ffmpeg's: a screen of filter graph that names no clip. Since a clip can
61
+ * have a viewport of its own, a config's `sequence` can name clips of two
62
+ * shapes, a phone clip among desktop ones most likely, so the sequence
63
+ * stops before the graph is built, and names two clips that differ and
64
+ * what to do.
65
+ */
66
+ function refuseTwoSizes(inputs) {
67
+ const [first] = inputs;
68
+ const other = inputs.find((input) => input.width !== first.width || input.height !== first.height);
69
+ if (!other)
70
+ return;
71
+ const size = (input) => `${input.width}x${input.height}`;
72
+ const portrait = [first, other].some((input) => input.height > input.width);
73
+ throw new Error(`A sequence stitches clips of one size: ${first.id} is ${size(first)} ` +
74
+ `and ${other.id} is ${size(other)}. ` +
75
+ (portrait
76
+ ? 'Leave the phone clips out of "sequence".'
77
+ : 'Give them one viewport, or leave one out of "sequence".'));
78
+ }
60
79
  /**
61
80
  * The `xfade` chain. Each transition's `offset` is measured from the start of
62
81
  * everything accumulated so far, and every crossfade shortens the total by its
@@ -73,6 +92,7 @@ function rateOf(fraction) {
73
92
  * is the one it always was.
74
93
  */
75
94
  export function buildFilter(inputs) {
95
+ refuseTwoSizes(inputs);
76
96
  if (inputs.length === 1)
77
97
  return { filter: "", label: "0:v", total: inputs[0].duration };
78
98
  const parts = [];
@@ -103,12 +123,7 @@ export async function buildSequence(ids, outDir, options = {}) {
103
123
  missing.push(id);
104
124
  continue;
105
125
  }
106
- inputs.push({
107
- id,
108
- path,
109
- duration: probeDuration(path),
110
- frameRate: probeFrameRate(path),
111
- });
126
+ inputs.push({ id, path, ...probeVideo(path) });
112
127
  }
113
128
  if (inputs.length < 2) {
114
129
  throw new Error(`Need at least two rendered clips to build a sequence; have ${inputs.length}.`);
@@ -1 +1 @@
1
- {"version":3,"file":"sequence.js","sourceRoot":"","sources":["../../src/render/sequence.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sequence.js","sourceRoot":"","sources":["../../src/render/sequence.ts"],"names":[],"mappings":"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"}
package/dsl.md CHANGED
@@ -129,6 +129,7 @@ already is. The press lasts 70 to 100 milliseconds, seeded.
129
129
  | Option | Default | What it sets |
130
130
  |---|---|---|
131
131
  | `dwell` | `80` to `150`, seeded | How long to rest on the target before pressing. |
132
+ | `modifiers` | none | Keys held through the press: `["Shift"]` for a shift-click, `["Meta"]`, `["Alt"]`, `["Control"]`. |
132
133
 
133
134
  ### `drag(target, from, to, options?)`
134
135
 
@@ -141,10 +142,15 @@ the drag survives a layout change: `{ x: 0.18, y: 0.5 }` to
141
142
  Mind the edges. A chart's axis labels live inside its own box, and a drag
142
143
  that starts at x 0.08 selects that text instead of the control.
143
144
 
145
+ `drag(from, to, options?)` is the other form: from one thing to another,
146
+ each a locator or a point, such as a file onto a dropzone or a card onto a
147
+ column. It is told apart by its second argument, which is a locator or a
148
+ point there, and a fraction `{ x, y }` in the first form.
149
+
144
150
  | Option | Default | What it sets |
145
151
  |---|---|---|
146
152
  | `duration` | `1400` | How long the stroke takes, from press to release. |
147
- | `modifiers` | none | Keys held through the stroke: `["Shift"]`, `["Alt"]`, or both. |
153
+ | `modifiers` | none | Keys held through the stroke: `["Shift"]`, `["Alt"]`, `["Meta"]`, `["Control"]`. |
148
154
 
149
155
  ### `lasso(target, planner, options?)`
150
156
 
@@ -163,6 +169,67 @@ selection from a frame after the loop closes.
163
169
  | `duration` | `1400` | How long the stroke takes, from press to release. |
164
170
  | `modifiers` | none | Keys held through the stroke: `["Shift"]`, `["Alt"]`, or both. |
165
171
 
172
+ ### `scroll(target, where, options?)`
173
+
174
+ Scroll a pane, or the page, on the clip's clock: one eased step a frame, so
175
+ the frames inside the scroll are the same on every run. `target` is the
176
+ element that scrolls, as a locator, or `"page"` for the document. `where` is
177
+ one of three:
178
+
179
+ - `{ by: px }` from where it is now: `{ by: 400 }` goes down, `{ by: -400 }`
180
+ back up.
181
+ - `{ to: "top" | "bottom" | px }` to one end, or to an offset.
182
+ - `{ into: locator, block? }` until that element sits at the `"start"`,
183
+ `"center"` or `"end"` of the pane's visible box. The default is
184
+ `"center"`.
185
+
186
+ The end is clamped to what the element can scroll, and the recording keeps
187
+ the end it reached. An element with nothing to scroll is refused: it is
188
+ nearly always a wrapper around the one that scrolls, which `inspect` shows.
189
+
190
+ The cursor stays where it is. For a scroll that reads as a mouse wheel, move
191
+ the cursor over the pane first. Never scroll with `offCamera` and
192
+ `scrollIntoView` or `mouse.wheel`: the browser animates those on its own
193
+ clock, and the frames inside them differ between runs. While recording, a
194
+ page's own smooth scrolling, from its stylesheet or from its script, is
195
+ instant.
196
+
197
+ | Option | Default | What it sets |
198
+ |---|---|---|
199
+ | `duration` | `700` | How long the scroll takes. Two frames at the least. `motion.scrollDurationMs` sets the default. |
200
+ | `axis` | `"y"` | `"x"` scrolls sideways. `"top"` is then the left end and `"bottom"` the right. |
201
+
202
+ ### `fileWindow(files, options?)`
203
+
204
+ Draw a file window over the page, listing `files`, to drag onto the app's
205
+ dropzone: the browser's own file dialog is outside the page, and nothing can
206
+ film it. It looks like a Finder window. Returns `{ item(i), close() }`:
207
+ `item(i)` is row `i` as a locator, for `click`, `hover` and `drag`.
208
+
209
+ ```ts
210
+ const files = await s.fileWindow(
211
+ [{ name: "cases.csv", content: csv }, { name: "sites.csv", content: sites }],
212
+ { title: "Case data" },
213
+ );
214
+ await s.click(files.item(0));
215
+ await s.click(files.item(1), { modifiers: ["Shift"] });
216
+ await s.drag(files.item(0), s.byTestId("dropzone"), { duration: 1000 });
217
+ ```
218
+
219
+ A click selects one row, a click with Shift extends to it, and one with
220
+ Meta adds or removes it. A press on a selected row drags the whole
221
+ selection, and letting go over the page sends the element under the pointer
222
+ the drag events a real drag sends, carrying the files as `File` objects, so
223
+ the app takes them through its own dropzone. The window then closes. A file
224
+ is `{ name, content?, type?, size? }`; the type and the Size and Kind columns
225
+ follow from the name and the content unless given. The window is staged:
226
+ say so wherever the clip is shown.
227
+
228
+ | Option | Default | What it sets |
229
+ |---|---|---|
230
+ | `title` | `"Files"` | The title bar. |
231
+ | `at` | `{ x: 0.52, y: 0.42 }` | The window's top left corner, as fractions of the viewport. |
232
+
166
233
  ## Keyboard
167
234
 
168
235
  ### `typeInto(target, text)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versioncam",
3
- "version": "0.5.3",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "description": "Record demo clips of a real web app: scripted, deterministic, re-recorded on every push.",
6
6
  "license": "FSL-1.1-ALv2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versioncam",
3
- "version": "0.5.3",
3
+ "version": "0.7.1",
4
4
  "description": "Record a short, true demo clip of a web app from one sentence: a skill that sets the repository up on its first run, writes the clip, and has a reviewer pass it from a contact sheet.",
5
5
  "author": {
6
6
  "name": "Peter Bulovec"
@@ -131,6 +131,15 @@ export default clip(
131
131
  `seed` fixes the motion, so two runs of the clip produce the same frames. Pick
132
132
  one and leave it alone.
133
133
 
134
+ A phone clip is a second clip file, `<id>-mobile.clip.ts`, with
135
+ `viewport: { width: 390, height: 844 }` in its options beside the title and
136
+ the seed. It uses the same test ids as `<id>`, and its own steps where the
137
+ phone layout differs: a menu behind a button, a list under the form rather
138
+ than beside it. A draft's contact sheet shows which. It is not listed in the
139
+ config's `sequence`, which stitches clips of one size. On version.cam,
140
+ `<id>-mobile` is shown in place of `<id>` on a narrow screen, so the name
141
+ is the whole of joining them.
142
+
134
143
  ## Write the beat sheet before you record
135
144
 
136
145
  `.versioncam/author/<id>/beats.json`, an array of `{ label, expect }`:
@@ -193,9 +202,14 @@ happened again the first time an agent used it.
193
202
  |---|---|
194
203
  | locator not found at step N | `inspect` at that moment; pick a control that exists; if none does, the beat is wrong |
195
204
  | beat produced no new state | missing `settle`, wrong target, or effect off-screen; check cuts and states |
196
- | settle timed out on `X` | add a `ready` hook, raise the timeout, or the source is live — record a HAR |
205
+ | settle timed out on `X` | add a `ready` hook, raise the timeout, or the source is live: name it in `replay.record` so its answer is recorded once and served after |
206
+ | two runs differ where the app fetched something (tiles, a count, an answer) | `replay.record` the request; `replay.pace` it too if the page reads it over time |
207
+ | the clip fails with "its replay does not hold" | the app fetches something new; `versioncam record <id> --replay record` and look at the diff of `replay/<id>.har` |
197
208
  | document reloaded mid-clip | something navigated; find the trigger and avoid it |
198
209
  | drag or lasso selected nothing | `measure`, re-author geometry as fractions of the real box |
210
+ | the frames inside a scroll differ between runs, or the page scrolls on its own | `s.scroll(pane, { by })`, `{ to }` or `{ into }`; never `scrollIntoView` or `mouse.wheel` from `offCamera`, which the browser animates on its own clock |
211
+ | the clip has to show files being uploaded | `s.fileWindow([...])`, click the rows (Shift to extend), then `s.drag(files.item(0), dropzone)`; never `setInputFiles` from `offCamera`, which shows the files arriving from nowhere. Say in the summary that the window is staged |
212
+ | `scroll` says the element does not scroll | it is a wrapper; `inspect` at that moment and scroll the child with `overflow: auto` |
199
213
  | duration drift | adjust holds, never the beats |
200
214
  | tooltip / menu open in a frame it should not be | move the cursor start; add a hold before the next action |
201
215
  | the caption covers something a beat points at | `theme.presentation.captionInset: { right: 40 }` in the config moves it to the bottom-right corner; it moves every clip's caption, so say so in what you write down |