@vfrey/vfrey 0.0.1 → 1.0.0-beta.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.
Files changed (603) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +78 -3
  3. package/accounts.example.json +61 -0
  4. package/accounts.first-run.json +41 -0
  5. package/bin/vfrey.mjs +36 -0
  6. package/integration-tools/README.md +43 -0
  7. package/integration-tools/image/pillow.md +32 -0
  8. package/integration-tools/image/playwright.md +33 -0
  9. package/integration-tools/image/resvg.md +36 -0
  10. package/integration-tools/image/satori.md +36 -0
  11. package/integration-tools/image/sharp.md +38 -0
  12. package/integration-tools/video/blender.md +34 -0
  13. package/integration-tools/video/ffmpeg.md +41 -0
  14. package/integration-tools/video/manim.md +37 -0
  15. package/integration-tools/video/motion-canvas.md +37 -0
  16. package/integration-tools/video/remotion.md +50 -0
  17. package/integration-tools/video/revideo.md +32 -0
  18. package/package.json +74 -6
  19. package/skills/vfrey-orchestrator/SKILL.md +15 -0
  20. package/skills/vfrey-orchestrator/channel-video/SKILL.md +41 -0
  21. package/skills/vfrey-product/SKILL.md +63 -0
  22. package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/cook-one.svg +14 -0
  23. package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/cook-two.svg +22 -0
  24. package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/index.html +70 -0
  25. package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/cook-one.svg +14 -0
  26. package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/cook-two.svg +22 -0
  27. package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/index.tsx +49 -0
  28. package/skills/vfrey-product/examples/concurrency-vs-parallelism/script.json +15 -0
  29. package/skills/vfrey-product/examples/octopus-facts/index.html +60 -0
  30. package/skills/vfrey-product/examples/octopus-facts/octopus.svg +8 -0
  31. package/skills/vfrey-product/examples/octopus-facts/script.json +14 -0
  32. package/skills/vfrey-product/push/SKILL.md +15 -0
  33. package/skills/vfrey-product/scenes/NOTICE +5 -0
  34. package/skills/vfrey-product/scenes/blocks.md +57 -0
  35. package/skills/vfrey-product/scenes/craft.md +118 -0
  36. package/skills/vfrey-product/scenes/motion.md +88 -0
  37. package/src/analytics/dashboard.ts +109 -0
  38. package/src/analytics/index.ts +260 -0
  39. package/src/api/client.ts +282 -0
  40. package/src/api/commands.ts +330 -0
  41. package/src/api/config.ts +34 -0
  42. package/src/api/creators.ts +463 -0
  43. package/src/api/credentials.ts +152 -0
  44. package/src/api/mail.ts +333 -0
  45. package/src/api/topics.ts +207 -0
  46. package/src/api/types.ts +205 -0
  47. package/src/cli.ts +467 -0
  48. package/src/cloud/executor.ts +281 -0
  49. package/src/cloud/job.ts +106 -0
  50. package/src/cloud/render-job.ts +187 -0
  51. package/src/config.ts +309 -0
  52. package/src/dashboard-link.ts +180 -0
  53. package/src/dashboard-start.ts +103 -0
  54. package/src/desk/channel.ts +64 -0
  55. package/src/desk/cli.ts +216 -0
  56. package/src/desk/creators-route.ts +129 -0
  57. package/src/desk/fonts/JetBrainsMono-OFL.txt +93 -0
  58. package/src/desk/fonts/google-sans-flex-latin.woff2 +0 -0
  59. package/src/desk/fonts/jetbrains-mono-medium.woff2 +0 -0
  60. package/src/desk/http.ts +460 -0
  61. package/src/desk/mail-route.ts +103 -0
  62. package/src/desk/overlay.ts +112 -0
  63. package/src/desk/preview.ts +422 -0
  64. package/src/desk/screens.ts +149 -0
  65. package/src/desk/secrets.ts +143 -0
  66. package/src/desk/series.ts +127 -0
  67. package/src/desk/state.ts +77 -0
  68. package/src/desk/studio-route.ts +119 -0
  69. package/src/desk/tick-runner.ts +66 -0
  70. package/src/desk/tick.ts +56 -0
  71. package/src/desk/topics-route.ts +41 -0
  72. package/src/desk/ui/app.css +505 -0
  73. package/src/desk/ui/app.js +185 -0
  74. package/src/desk/ui/apple-icon.png +0 -0
  75. package/src/desk/ui/components.js +79 -0
  76. package/src/desk/ui/core.js +156 -0
  77. package/src/desk/ui/favicon.ico +0 -0
  78. package/src/desk/ui/icon.svg +1 -0
  79. package/src/desk/ui/icons.js +49 -0
  80. package/src/desk/ui/index.html +41 -0
  81. package/src/desk/ui/metrics-math.d.ts +22 -0
  82. package/src/desk/ui/metrics-math.js +135 -0
  83. package/src/desk/ui/theme.js +5 -0
  84. package/src/desk/ui/views-creators.js +209 -0
  85. package/src/desk/ui/views-keys.js +114 -0
  86. package/src/desk/ui/views-mail.js +188 -0
  87. package/src/desk/ui/views-misc.js +123 -0
  88. package/src/desk/ui/views-review.js +119 -0
  89. package/src/desk/ui/views-runs.js +350 -0
  90. package/src/desk/ui/views-screens.js +163 -0
  91. package/src/desk/ui/views-series.js +318 -0
  92. package/src/desk/ui/views-topics.js +87 -0
  93. package/src/desk/verbs.ts +361 -0
  94. package/src/doctor.ts +212 -0
  95. package/src/drain.ts +14 -0
  96. package/src/engage/index.ts +217 -0
  97. package/src/generate/index.ts +110 -0
  98. package/src/generate/script.ts +152 -0
  99. package/src/generate/title.ts +78 -0
  100. package/src/help.ts +233 -0
  101. package/src/home.ts +45 -0
  102. package/src/llm/client.ts +177 -0
  103. package/src/log.ts +7 -0
  104. package/src/oauth/tokens.ts +189 -0
  105. package/src/oauth/vault.ts +72 -0
  106. package/src/pipeline/review.ts +642 -0
  107. package/src/pipeline/run.ts +443 -0
  108. package/src/pipeline/series-guard.ts +26 -0
  109. package/src/pipeline/source-fetch.ts +69 -0
  110. package/src/pipeline/stages.ts +728 -0
  111. package/src/pipeline/topics-source.ts +90 -0
  112. package/src/product/commands.ts +296 -0
  113. package/src/product/config-file.ts +38 -0
  114. package/src/product/engine-choice.ts +89 -0
  115. package/src/product/engines.ts +103 -0
  116. package/src/product/insights.ts +50 -0
  117. package/src/product/keys.ts +67 -0
  118. package/src/product/paths.ts +18 -0
  119. package/src/product/probes.ts +88 -0
  120. package/src/product/push-flow.ts +90 -0
  121. package/src/product/render-video.ts +21 -0
  122. package/src/product/short.ts +62 -0
  123. package/src/product/update.ts +32 -0
  124. package/src/project/canonical.ts +14 -0
  125. package/src/project/commands.ts +177 -0
  126. package/src/project/dashboard-status.ts +28 -0
  127. package/src/project/discover.ts +19 -0
  128. package/src/project/index-file.ts +73 -0
  129. package/src/project/marker.ts +87 -0
  130. package/src/project/migrate.ts +201 -0
  131. package/src/project/names.ts +27 -0
  132. package/src/project/notice.ts +16 -0
  133. package/src/project/open.ts +88 -0
  134. package/src/project/scaffold.ts +112 -0
  135. package/src/project/templates.ts +42 -0
  136. package/src/project/videos.ts +64 -0
  137. package/src/publish/facebook.ts +91 -0
  138. package/src/publish/index.ts +12 -0
  139. package/src/publish/instagram.ts +97 -0
  140. package/src/publish/rate.ts +62 -0
  141. package/src/publish/session.ts +242 -0
  142. package/src/publish/tiktok.ts +202 -0
  143. package/src/publish/youtube.ts +177 -0
  144. package/src/queue/desk-ops.ts +1170 -0
  145. package/src/queue/seal.ts +3 -0
  146. package/src/queue/store.ts +1006 -0
  147. package/src/remotion/AiLabel.tsx +26 -0
  148. package/src/remotion/Root.tsx +73 -0
  149. package/src/remotion/defaults.ts +26 -0
  150. package/src/remotion/films/claude/ClaudeFilm.tsx +1014 -0
  151. package/src/remotion/films/engine/EngineMark.tsx +17 -0
  152. package/src/remotion/films/rea/ReaShort.tsx +552 -0
  153. package/src/remotion/films/rea/drawings.tsx +907 -0
  154. package/src/remotion/films/rea/timing.ts +144 -0
  155. package/src/remotion/index.ts +4 -0
  156. package/src/remotion/templates/EditorialSplit.tsx +39 -0
  157. package/src/remotion/templates/KineticType.tsx +39 -0
  158. package/src/remotion/templates/SignalBoard.tsx +50 -0
  159. package/src/remotion/timing.ts +22 -0
  160. package/src/render/chrome.ts +78 -0
  161. package/src/render/engine-mark.ts +29 -0
  162. package/src/render/framey-template.ts +130 -0
  163. package/src/render/html-engine.ts +149 -0
  164. package/src/render/node-crypto-stub.ts +11 -0
  165. package/src/render/probe.ts +29 -0
  166. package/src/render/props.ts +17 -0
  167. package/src/render/vertical-short.ts +35 -0
  168. package/src/render/webpack.ts +33 -0
  169. package/src/render/worker.ts +65 -0
  170. package/src/scene/assets/fonts/TitanOne-OFL.txt +94 -0
  171. package/src/scene/assets/fonts/titan-one.woff2 +0 -0
  172. package/src/scene/assets/gsap/LICENSE.txt +8 -0
  173. package/src/scene/assets/gsap/gsap.min.js +11 -0
  174. package/src/scene/audio.ts +54 -0
  175. package/src/scene/blocks/LICENSE +190 -0
  176. package/src/scene/blocks/NOTICE +5 -0
  177. package/src/scene/blocks/bar-chart-race/bar-chart-race.html +585 -0
  178. package/src/scene/blocks/beat-freeze-cut/beat-freeze-cut.html +874 -0
  179. package/src/scene/blocks/camcorder-hud/camcorder-hud.html +164 -0
  180. package/src/scene/blocks/camera-dolly-zoom/camera-dolly-zoom.html +355 -0
  181. package/src/scene/blocks/code-diff/code-diff.html +817 -0
  182. package/src/scene/blocks/code-snippet-apple-terminal-pro/code-snippet-apple-terminal-pro.html +248 -0
  183. package/src/scene/blocks/code-typing/code-typing.html +764 -0
  184. package/src/scene/blocks/data-chart/data-chart.html +452 -0
  185. package/src/scene/blocks/flowchart-vertical/flowchart-vertical.html +454 -0
  186. package/src/scene/blocks/hw-scribble-transition/hw-scribble-transition.html +153 -0
  187. package/src/scene/blocks/index.json +824 -0
  188. package/src/scene/blocks/lt-accent-underline/lt-accent-underline.html +107 -0
  189. package/src/scene/blocks/lt-bold-block/lt-bold-block.html +109 -0
  190. package/src/scene/blocks/lt-clean-bar/lt-clean-bar.html +112 -0
  191. package/src/scene/blocks/lt-dark-card/lt-dark-card.html +111 -0
  192. package/src/scene/blocks/lt-kicker-name/lt-kicker-name.html +114 -0
  193. package/src/scene/blocks/lt-mask-reveal/lt-mask-reveal.html +120 -0
  194. package/src/scene/blocks/lt-neon-border/lt-neon-border.html +391 -0
  195. package/src/scene/blocks/lt-stack-bars/lt-stack-bars.html +109 -0
  196. package/src/scene/blocks/macos-notification/macos-notification.html +194 -0
  197. package/src/scene/blocks/mk-background/mk-background.html +236 -0
  198. package/src/scene/blocks/mk-callout-highlight/mk-callout-highlight.html +183 -0
  199. package/src/scene/blocks/mk-clone-wall-transition/mk-clone-wall-transition.html +226 -0
  200. package/src/scene/blocks/mk-line-graph/mk-line-graph.html +293 -0
  201. package/src/scene/blocks/mk-progress-stat/mk-progress-stat.html +186 -0
  202. package/src/scene/blocks/mk-specs-list/mk-specs-list.html +223 -0
  203. package/src/scene/blocks/news-ticker/news-ticker.html +409 -0
  204. package/src/scene/blocks/organic-light-leak-overlay/organic-light-leak-overlay.html +126 -0
  205. package/src/scene/blocks/oscilloscope-trace/oscilloscope-trace.html +519 -0
  206. package/src/scene/blocks/snow/snow.html +205 -0
  207. package/src/scene/blocks/split-flap-board/split-flap-board.html +347 -0
  208. package/src/scene/blocks/spotify-card/spotify-card.html +294 -0
  209. package/src/scene/blocks/starfield/starfield.html +188 -0
  210. package/src/scene/blocks/thread-message-stack/thread-message-stack.html +239 -0
  211. package/src/scene/blocks/week-in-merges/week-in-merges.html +470 -0
  212. package/src/scene/blocks/x-post/x-post.html +437 -0
  213. package/src/scene/blocks/yt-comment-card/yt-comment-card.html +319 -0
  214. package/src/scene/blocks/yt-lcd-background/yt-lcd-background.html +307 -0
  215. package/src/scene/blocks/yt-prism-title/yt-prism-title.html +205 -0
  216. package/src/scene/blocks.ts +229 -0
  217. package/src/scene/command.ts +272 -0
  218. package/src/scene/figure.ts +181 -0
  219. package/src/scene/glyphs.ts +107 -0
  220. package/src/scene/init.ts +86 -0
  221. package/src/scene/inspect/css-tree.d.ts +6 -0
  222. package/src/scene/inspect/page-scripts.ts +272 -0
  223. package/src/scene/inspect/report.ts +405 -0
  224. package/src/scene/inspect/run.ts +200 -0
  225. package/src/scene/inspect/source.ts +236 -0
  226. package/src/scene/label.ts +131 -0
  227. package/src/scene/net-guard.ts +74 -0
  228. package/src/scene/render.ts +292 -0
  229. package/src/scene/runtime/browser.ts +199 -0
  230. package/src/scene/runtime/core.ts +181 -0
  231. package/src/scene/runtime/react.tsx +115 -0
  232. package/src/scene/runtime/transitions.ts +133 -0
  233. package/src/scene/sanitize.ts +181 -0
  234. package/src/scene/scene.ts +152 -0
  235. package/src/scene/studio-command.ts +48 -0
  236. package/src/scene/studio-skin.ts +59 -0
  237. package/src/scene/studio.ts +337 -0
  238. package/src/scene/supply.ts +111 -0
  239. package/src/scene/timing.ts +288 -0
  240. package/src/scene/transitions/LICENSE +190 -0
  241. package/src/scene/transitions/NOTICE +5 -0
  242. package/src/scene/transitions/SOURCE.json +24 -0
  243. package/src/scene/transitions/transitions.global.js +38 -0
  244. package/src/scene/transitions.ts +26 -0
  245. package/src/service/all-projects.ts +153 -0
  246. package/src/service/commands.ts +256 -0
  247. package/src/service/due.ts +82 -0
  248. package/src/service/lock.ts +101 -0
  249. package/src/service/log-file.ts +56 -0
  250. package/src/service/state.ts +41 -0
  251. package/src/service/units.ts +90 -0
  252. package/src/service/worker.ts +51 -0
  253. package/src/skill-agents.ts +74 -0
  254. package/src/skill-install.ts +334 -0
  255. package/src/sync-write.ts +21 -0
  256. package/src/tick-report.ts +205 -0
  257. package/src/tts.ts +124 -0
  258. package/vendor/@vfrey/contracts/dist/account.d.ts +97 -0
  259. package/vendor/@vfrey/contracts/dist/account.js +27 -0
  260. package/vendor/@vfrey/contracts/dist/checks.d.ts +8 -0
  261. package/vendor/@vfrey/contracts/dist/checks.js +1 -0
  262. package/vendor/@vfrey/contracts/dist/credentials/crypto.d.ts +4 -0
  263. package/vendor/@vfrey/contracts/dist/credentials/crypto.js +38 -0
  264. package/vendor/@vfrey/contracts/dist/domain/advice-persona.d.ts +10 -0
  265. package/vendor/@vfrey/contracts/dist/domain/advice-persona.js +24 -0
  266. package/vendor/@vfrey/contracts/dist/domain/backoff.d.ts +13 -0
  267. package/vendor/@vfrey/contracts/dist/domain/backoff.js +27 -0
  268. package/vendor/@vfrey/contracts/dist/domain/cadence.d.ts +29 -0
  269. package/vendor/@vfrey/contracts/dist/domain/cadence.js +95 -0
  270. package/vendor/@vfrey/contracts/dist/domain/creators.d.ts +146 -0
  271. package/vendor/@vfrey/contracts/dist/domain/creators.js +2 -0
  272. package/vendor/@vfrey/contracts/dist/domain/disclosure.d.ts +127 -0
  273. package/vendor/@vfrey/contracts/dist/domain/disclosure.js +250 -0
  274. package/vendor/@vfrey/contracts/dist/domain/errors.d.ts +34 -0
  275. package/vendor/@vfrey/contracts/dist/domain/errors.js +68 -0
  276. package/vendor/@vfrey/contracts/dist/domain/redact.d.ts +1 -0
  277. package/vendor/@vfrey/contracts/dist/domain/redact.js +21 -0
  278. package/vendor/@vfrey/contracts/dist/domain/revenue.d.ts +13 -0
  279. package/vendor/@vfrey/contracts/dist/domain/revenue.js +16 -0
  280. package/vendor/@vfrey/contracts/dist/domain/safety.d.ts +20 -0
  281. package/vendor/@vfrey/contracts/dist/domain/safety.js +61 -0
  282. package/vendor/@vfrey/contracts/dist/domain/similarity.d.ts +13 -0
  283. package/vendor/@vfrey/contracts/dist/domain/similarity.js +100 -0
  284. package/vendor/@vfrey/contracts/dist/domain/templates.d.ts +18 -0
  285. package/vendor/@vfrey/contracts/dist/domain/templates.js +67 -0
  286. package/vendor/@vfrey/contracts/dist/domain/tiktok-chunks.d.ts +19 -0
  287. package/vendor/@vfrey/contracts/dist/domain/tiktok-chunks.js +52 -0
  288. package/vendor/@vfrey/contracts/dist/domain/topics.d.ts +78 -0
  289. package/vendor/@vfrey/contracts/dist/domain/topics.js +4 -0
  290. package/vendor/@vfrey/contracts/dist/domain/types.d.ts +216 -0
  291. package/vendor/@vfrey/contracts/dist/domain/types.js +1 -0
  292. package/vendor/@vfrey/contracts/dist/domain/variation.d.ts +57 -0
  293. package/vendor/@vfrey/contracts/dist/domain/variation.js +278 -0
  294. package/vendor/@vfrey/contracts/dist/events.d.ts +20 -0
  295. package/vendor/@vfrey/contracts/dist/events.js +1 -0
  296. package/vendor/@vfrey/contracts/dist/http.d.ts +11 -0
  297. package/vendor/@vfrey/contracts/dist/http.js +37 -0
  298. package/vendor/@vfrey/contracts/dist/index.d.ts +22 -0
  299. package/vendor/@vfrey/contracts/dist/index.js +22 -0
  300. package/vendor/@vfrey/contracts/dist/log.d.ts +2 -0
  301. package/vendor/@vfrey/contracts/dist/log.js +10 -0
  302. package/vendor/@vfrey/contracts/dist/mail.d.ts +55 -0
  303. package/vendor/@vfrey/contracts/dist/mail.js +1 -0
  304. package/vendor/@vfrey/contracts/package.json +18 -0
  305. package/vendor/@vfrey/ingest/dist/index.d.ts +41 -0
  306. package/vendor/@vfrey/ingest/dist/index.js +221 -0
  307. package/vendor/@vfrey/ingest/dist/parse.d.ts +5 -0
  308. package/vendor/@vfrey/ingest/dist/parse.js +165 -0
  309. package/vendor/@vfrey/ingest/package.json +21 -0
  310. package/vendor/framey/LICENSE +190 -0
  311. package/vendor/framey/NOTICE +15 -0
  312. package/vendor/framey/SOURCE.json +24 -0
  313. package/vendor/framey/STUDIO-SOURCE.json +9 -0
  314. package/vendor/framey/bin/framey-localize-fonts.mjs +12 -0
  315. package/vendor/framey/bin/framey.mjs +16 -0
  316. package/vendor/framey/dist/add-VMJD5KBS.js +75 -0
  317. package/vendor/framey/dist/animatedGifPrep-QPKGJ6PM.js +43 -0
  318. package/vendor/framey/dist/api.d.ts +211 -0
  319. package/vendor/framey/dist/api.js +76 -0
  320. package/vendor/framey/dist/appHistory-WW56FZTT.js +31 -0
  321. package/vendor/framey/dist/asset-WQKGOELE.js +26 -0
  322. package/vendor/framey/dist/audio-RSJWVWRA.js +1936 -0
  323. package/vendor/framey/dist/batchRender-3HPICDCT.js +366 -0
  324. package/vendor/framey/dist/beats-HL2BDUZI.js +273 -0
  325. package/vendor/framey/dist/benchmark-RWCWHI5Z.js +230 -0
  326. package/vendor/framey/dist/browser-ECYET3Q2.js +206 -0
  327. package/vendor/framey/dist/bundleWithLocalizedFonts-ALYBQEQT.js +52 -0
  328. package/vendor/framey/dist/captureCompositionFrame-4DA6SEH2.js +94 -0
  329. package/vendor/framey/dist/catalog-5JVDEUWK.js +979 -0
  330. package/vendor/framey/dist/catch-up-NEBCL5NO.js +106 -0
  331. package/vendor/framey/dist/check-F5N544ML.js +111 -0
  332. package/vendor/framey/dist/checkBrowser-LZ66CMJG.js +110 -0
  333. package/vendor/framey/dist/chunk-2K7Z2HVP.js +372 -0
  334. package/vendor/framey/dist/chunk-2UZQHDM7.js +323 -0
  335. package/vendor/framey/dist/chunk-2X4PQEYM.js +34 -0
  336. package/vendor/framey/dist/chunk-3422KMTD.js +168 -0
  337. package/vendor/framey/dist/chunk-3FYD4YEK.js +72 -0
  338. package/vendor/framey/dist/chunk-3GXQPCJC.js +682 -0
  339. package/vendor/framey/dist/chunk-3LM6YPBR.js +179 -0
  340. package/vendor/framey/dist/chunk-442JNXRM.js +20 -0
  341. package/vendor/framey/dist/chunk-4BNN5FGX.js +182 -0
  342. package/vendor/framey/dist/chunk-4P7NX5BM.js +11532 -0
  343. package/vendor/framey/dist/chunk-4UMPIPCQ.js +88 -0
  344. package/vendor/framey/dist/chunk-4YYJOFKS.js +28 -0
  345. package/vendor/framey/dist/chunk-54HJ63Q2.js +29 -0
  346. package/vendor/framey/dist/chunk-5ETJX6WM.js +89 -0
  347. package/vendor/framey/dist/chunk-5SG54JRS.js +68 -0
  348. package/vendor/framey/dist/chunk-634HSJ3J.js +58 -0
  349. package/vendor/framey/dist/chunk-66LREXGS.js +48 -0
  350. package/vendor/framey/dist/chunk-6BEFEWFB.js +25 -0
  351. package/vendor/framey/dist/chunk-6BNTGNGD.js +286 -0
  352. package/vendor/framey/dist/chunk-6DDPOHMK.js +140 -0
  353. package/vendor/framey/dist/chunk-6GV5XSOR.js +214 -0
  354. package/vendor/framey/dist/chunk-6JR2XUEE.js +127 -0
  355. package/vendor/framey/dist/chunk-73QVATIM.js +362 -0
  356. package/vendor/framey/dist/chunk-7FMUTGOH.js +279 -0
  357. package/vendor/framey/dist/chunk-7JQFJ6JJ.js +291 -0
  358. package/vendor/framey/dist/chunk-AHPGAESX.js +113 -0
  359. package/vendor/framey/dist/chunk-AHX34EFP.js +15 -0
  360. package/vendor/framey/dist/chunk-AJ5CSAEZ.js +487 -0
  361. package/vendor/framey/dist/chunk-B7RJV74O.js +5047 -0
  362. package/vendor/framey/dist/chunk-BL4U6U52.js +254 -0
  363. package/vendor/framey/dist/chunk-BSQ7XANM.js +210 -0
  364. package/vendor/framey/dist/chunk-BZ56MTDX.js +6 -0
  365. package/vendor/framey/dist/chunk-C5BO7PMT.js +282 -0
  366. package/vendor/framey/dist/chunk-CFUCHO4T.js +358 -0
  367. package/vendor/framey/dist/chunk-CZGHFATT.js +31 -0
  368. package/vendor/framey/dist/chunk-DLA44CMS.js +5649 -0
  369. package/vendor/framey/dist/chunk-DPVXBVLL.js +31292 -0
  370. package/vendor/framey/dist/chunk-DQBY7FRR.js +397 -0
  371. package/vendor/framey/dist/chunk-EAMLBAHA.js +154 -0
  372. package/vendor/framey/dist/chunk-EOVYQ3IG.js +143 -0
  373. package/vendor/framey/dist/chunk-EVMY7IPL.js +612 -0
  374. package/vendor/framey/dist/chunk-FG4BBFZI.js +122 -0
  375. package/vendor/framey/dist/chunk-FI4I7CME.js +56 -0
  376. package/vendor/framey/dist/chunk-FLREUEHW.js +2081 -0
  377. package/vendor/framey/dist/chunk-FUFP7A7S.js +1126 -0
  378. package/vendor/framey/dist/chunk-FV3PIKGH.js +56 -0
  379. package/vendor/framey/dist/chunk-FXMKHHPO.js +123 -0
  380. package/vendor/framey/dist/chunk-GGZAM5VH.js +1187 -0
  381. package/vendor/framey/dist/chunk-GKZEVJIB.js +39 -0
  382. package/vendor/framey/dist/chunk-GMECXFAN.js +57 -0
  383. package/vendor/framey/dist/chunk-GNEFDLFB.js +621 -0
  384. package/vendor/framey/dist/chunk-GPMLSOEG.js +19274 -0
  385. package/vendor/framey/dist/chunk-GPYVT5EE.js +59 -0
  386. package/vendor/framey/dist/chunk-H6IEMZEV.js +40 -0
  387. package/vendor/framey/dist/chunk-HEFQCIXR.js +170 -0
  388. package/vendor/framey/dist/chunk-HFQ4CWHA.js +118 -0
  389. package/vendor/framey/dist/chunk-HPDTCOKE.js +304 -0
  390. package/vendor/framey/dist/chunk-HTUHQKDB.js +548 -0
  391. package/vendor/framey/dist/chunk-HU2SPFNG.js +103 -0
  392. package/vendor/framey/dist/chunk-HVW7ABXD.js +19 -0
  393. package/vendor/framey/dist/chunk-IG7NDKXO.js +26 -0
  394. package/vendor/framey/dist/chunk-II3J3WXK.js +246 -0
  395. package/vendor/framey/dist/chunk-IR35BCLK.js +13 -0
  396. package/vendor/framey/dist/chunk-ISN5GRYI.js +20 -0
  397. package/vendor/framey/dist/chunk-IUONB55J.js +669 -0
  398. package/vendor/framey/dist/chunk-IVSUQR2B.js +161 -0
  399. package/vendor/framey/dist/chunk-IXU6PQSW.js +259 -0
  400. package/vendor/framey/dist/chunk-IZCW2NKT.js +29 -0
  401. package/vendor/framey/dist/chunk-JGXJU5PF.js +68 -0
  402. package/vendor/framey/dist/chunk-JHOCK3DG.js +27 -0
  403. package/vendor/framey/dist/chunk-JVBRJMOP.js +121 -0
  404. package/vendor/framey/dist/chunk-K4MJX7SR.js +17 -0
  405. package/vendor/framey/dist/chunk-K4WKGXRC.js +18 -0
  406. package/vendor/framey/dist/chunk-KGPLIH7B.js +127 -0
  407. package/vendor/framey/dist/chunk-KLIKCX6Q.js +63 -0
  408. package/vendor/framey/dist/chunk-KXTKKBOC.js +61 -0
  409. package/vendor/framey/dist/chunk-KZI44OPA.js +185 -0
  410. package/vendor/framey/dist/chunk-LEKZNRKI.js +45 -0
  411. package/vendor/framey/dist/chunk-LFGJWOVA.js +61 -0
  412. package/vendor/framey/dist/chunk-LLMSX6V6.js +17 -0
  413. package/vendor/framey/dist/chunk-LTGTN2TH.js +195 -0
  414. package/vendor/framey/dist/chunk-M2WBOQ2Q.js +54 -0
  415. package/vendor/framey/dist/chunk-MI5FTLYK.js +399 -0
  416. package/vendor/framey/dist/chunk-MLQP2QUF.js +1809 -0
  417. package/vendor/framey/dist/chunk-MMQ52NIZ.js +956 -0
  418. package/vendor/framey/dist/chunk-ND46XKK3.js +742 -0
  419. package/vendor/framey/dist/chunk-NYEXNSHM.js +504 -0
  420. package/vendor/framey/dist/chunk-O4YRB27L.js +57 -0
  421. package/vendor/framey/dist/chunk-O7BGSQ5F.js +92 -0
  422. package/vendor/framey/dist/chunk-OFQWTKPW.js +117 -0
  423. package/vendor/framey/dist/chunk-P5ZBLXJ6.js +91 -0
  424. package/vendor/framey/dist/chunk-P6LJ4Y32.js +4662 -0
  425. package/vendor/framey/dist/chunk-PKZ7RK5N.js +281 -0
  426. package/vendor/framey/dist/chunk-PURHAXFL.js +15 -0
  427. package/vendor/framey/dist/chunk-Q5X2B5WT.js +29 -0
  428. package/vendor/framey/dist/chunk-QAMKOG67.js +248 -0
  429. package/vendor/framey/dist/chunk-R6YBK4XX.js +514 -0
  430. package/vendor/framey/dist/chunk-RGLQURIX.js +2997 -0
  431. package/vendor/framey/dist/chunk-RNV3DM4S.js +3573 -0
  432. package/vendor/framey/dist/chunk-ROEW726S.js +4638 -0
  433. package/vendor/framey/dist/chunk-RQVDTPGD.js +6 -0
  434. package/vendor/framey/dist/chunk-RVZHGOGR.js +26 -0
  435. package/vendor/framey/dist/chunk-RXATE6FZ.js +385 -0
  436. package/vendor/framey/dist/chunk-S3S4TGEH.js +424 -0
  437. package/vendor/framey/dist/chunk-SDYDNHEE.js +11202 -0
  438. package/vendor/framey/dist/chunk-SSWBU67I.js +173 -0
  439. package/vendor/framey/dist/chunk-ST35C4K6.js +28 -0
  440. package/vendor/framey/dist/chunk-SZNTYTIV.js +990 -0
  441. package/vendor/framey/dist/chunk-T2EUWCJE.js +29 -0
  442. package/vendor/framey/dist/chunk-T6AGUTZJ.js +444 -0
  443. package/vendor/framey/dist/chunk-THJALHFJ.js +11131 -0
  444. package/vendor/framey/dist/chunk-TIQB63MX.js +18128 -0
  445. package/vendor/framey/dist/chunk-U242RTCT.js +134 -0
  446. package/vendor/framey/dist/chunk-UJZREAV6.js +3901 -0
  447. package/vendor/framey/dist/chunk-UMD3V7CU.js +264 -0
  448. package/vendor/framey/dist/chunk-UOFAE5RF.js +42 -0
  449. package/vendor/framey/dist/chunk-UTEIUGAZ.js +29 -0
  450. package/vendor/framey/dist/chunk-VAS6HQX3.js +138 -0
  451. package/vendor/framey/dist/chunk-VC76R6W2.js +27 -0
  452. package/vendor/framey/dist/chunk-VFZ7W5W7.js +226 -0
  453. package/vendor/framey/dist/chunk-VHNLZAE2.js +348 -0
  454. package/vendor/framey/dist/chunk-VMC4WSH5.js +278 -0
  455. package/vendor/framey/dist/chunk-VORQIH64.js +108 -0
  456. package/vendor/framey/dist/chunk-VTXFRIHO.js +186 -0
  457. package/vendor/framey/dist/chunk-W2LGJSEN.js +182 -0
  458. package/vendor/framey/dist/chunk-WKBM7NDG.js +32 -0
  459. package/vendor/framey/dist/chunk-WMQNIQDJ.js +2057 -0
  460. package/vendor/framey/dist/chunk-WN4XZC5F.js +504 -0
  461. package/vendor/framey/dist/chunk-WQ6ZXHFQ.js +133 -0
  462. package/vendor/framey/dist/chunk-X7HBSTCZ.js +53 -0
  463. package/vendor/framey/dist/chunk-X7MTZ4T2.js +6 -0
  464. package/vendor/framey/dist/chunk-XPTOJZJF.js +308 -0
  465. package/vendor/framey/dist/chunk-XT6MXC7A.js +6 -0
  466. package/vendor/framey/dist/chunk-XT7X6QCP.js +6 -0
  467. package/vendor/framey/dist/chunk-XTMDWNEC.js +2820 -0
  468. package/vendor/framey/dist/chunk-XUHOIDKG.js +6 -0
  469. package/vendor/framey/dist/chunk-XX7LTW5Y.js +138 -0
  470. package/vendor/framey/dist/chunk-Y3YGTZEG.js +382 -0
  471. package/vendor/framey/dist/chunk-Y5SBSGN7.js +346 -0
  472. package/vendor/framey/dist/chunk-YCMVI4MZ.js +625 -0
  473. package/vendor/framey/dist/chunk-YOR4CTCE.js +261 -0
  474. package/vendor/framey/dist/chunk-YPRBGITP.js +2748 -0
  475. package/vendor/framey/dist/chunk-ZKDMAOFU.js +32 -0
  476. package/vendor/framey/dist/chunk-ZW6DURPF.js +1318 -0
  477. package/vendor/framey/dist/clean-ZJOGDKDM.js +336 -0
  478. package/vendor/framey/dist/cli.js +439 -0
  479. package/vendor/framey/dist/cloudrun-HGJ3B6ZV.js +767 -0
  480. package/vendor/framey/dist/commands/contrast-audit.browser.js +643 -0
  481. package/vendor/framey/dist/commands/layout-audit.browser.js +2914 -0
  482. package/vendor/framey/dist/commands/motion-sample.browser.js +74 -0
  483. package/vendor/framey/dist/commands/motion-signature.browser.js +475 -0
  484. package/vendor/framey/dist/compare-W7UY3BAZ.js +463 -0
  485. package/vendor/framey/dist/compiler-TM3W6GYF.js +136 -0
  486. package/vendor/framey/dist/component-QF5VGYMU.js +176 -0
  487. package/vendor/framey/dist/compositions-XT3PMNDF.js +238 -0
  488. package/vendor/framey/dist/contactSheet-7W6GYHBQ.js +24 -0
  489. package/vendor/framey/dist/deploy-6FJPWKF2.js +299 -0
  490. package/vendor/framey/dist/destroy-HE2RIPKZ.js +57 -0
  491. package/vendor/framey/dist/deterministicFonts-RADD3BSX.js +50 -0
  492. package/vendor/framey/dist/dist-4S2FGGVY.js +422 -0
  493. package/vendor/framey/dist/dist-BE4B2EZE.js +32 -0
  494. package/vendor/framey/dist/dist-EWXJGN63.js +129 -0
  495. package/vendor/framey/dist/docs-VKIRFEEX.js +131 -0
  496. package/vendor/framey/dist/doctor-2UNJ2G7U.js +74 -0
  497. package/vendor/framey/dist/dom-37K6YOH4.js +14 -0
  498. package/vendor/framey/dist/dotEnv-7PZQTVIM.js +54 -0
  499. package/vendor/framey/dist/events-ACY3P2PA.js +76 -0
  500. package/vendor/framey/dist/events-IHB3B2MK.js +69 -0
  501. package/vendor/framey/dist/ffmpeg-DR5LGGSK.js +63 -0
  502. package/vendor/framey/dist/figma-BODVUPHB.js +63 -0
  503. package/vendor/framey/dist/fontData.generated-NXAOI4GH.js +13 -0
  504. package/vendor/framey/dist/fontLocalizeCli.js +142 -0
  505. package/vendor/framey/dist/gpuPolicy-XLEXORZY.js +49 -0
  506. package/vendor/framey/dist/grade-compare-GAXFQ74C.js +661 -0
  507. package/vendor/framey/dist/gsapParser-IEUSLYDJ.js +2885 -0
  508. package/vendor/framey/dist/gsapParserAcorn-LZJKCFQI.js +30 -0
  509. package/vendor/framey/dist/help-VCB5X2HS.js +184 -0
  510. package/vendor/framey/dist/history-ENGZEVFT.js +471 -0
  511. package/vendor/framey/dist/hyperframe.manifest.json +22 -0
  512. package/vendor/framey/dist/hyperframe.runtime.iife.js +1132 -0
  513. package/vendor/framey/dist/hyperframes-player.global.js +478 -0
  514. package/vendor/framey/dist/hyperframes-slideshow.global.js +148 -0
  515. package/vendor/framey/dist/info-WPCJHBMU.js +182 -0
  516. package/vendor/framey/dist/init-G4WGLYGD.js +1176 -0
  517. package/vendor/framey/dist/inspect-5IQ6CNEQ.js +106 -0
  518. package/vendor/framey/dist/installer-ML4HWUV7.js +40 -0
  519. package/vendor/framey/dist/keyframes-Q6XJO7VW.js +2755 -0
  520. package/vendor/framey/dist/lambda-A7JC74JF.js +468 -0
  521. package/vendor/framey/dist/layout-YET6XNXA.js +95 -0
  522. package/vendor/framey/dist/lint-I7FZN2VJ.js +162 -0
  523. package/vendor/framey/dist/lintProject-PDB3UN72.js +30 -0
  524. package/vendor/framey/dist/manager-CD6LJ7ZN.js +45 -0
  525. package/vendor/framey/dist/manager-WA4F2GST.js +67 -0
  526. package/vendor/framey/dist/media-treatment-BXYS4WVA.js +1030 -0
  527. package/vendor/framey/dist/media-use-LWH3MANK.js +113 -0
  528. package/vendor/framey/dist/models-K363FYM4.js +195 -0
  529. package/vendor/framey/dist/motionShot-GPAVWXLH.js +640 -0
  530. package/vendor/framey/dist/normalize-FASIPBUM.js +24 -0
  531. package/vendor/framey/dist/normalize-audio-VDWYRHN6.js +268 -0
  532. package/vendor/framey/dist/open-E3TB6P6J.js +128 -0
  533. package/vendor/framey/dist/parakeetRunner-UP34UOI2.js +58 -0
  534. package/vendor/framey/dist/pipeline-VXIUYEBU.js +64 -0
  535. package/vendor/framey/dist/play-BUTDQ4DV.js +353 -0
  536. package/vendor/framey/dist/policies-OLDYGFK5.js +309 -0
  537. package/vendor/framey/dist/preflight-2KZ2GWAZ.js +55 -0
  538. package/vendor/framey/dist/present-GXYSM7NX.js +353 -0
  539. package/vendor/framey/dist/preview-EKZ33JOJ.js +1910 -0
  540. package/vendor/framey/dist/producer-SKU5EV3J.js +13 -0
  541. package/vendor/framey/dist/progress-ZWB4Z4ET.js +76 -0
  542. package/vendor/framey/dist/projectConfig-3MZAYVTS.js +60 -0
  543. package/vendor/framey/dist/publication-DTKWNF74.js +33 -0
  544. package/vendor/framey/dist/remove-background-OY5L7XN2.js +265 -0
  545. package/vendor/framey/dist/render-CDV4GDFX.js +187 -0
  546. package/vendor/framey/dist/render-YE5YAMOD.js +2620 -0
  547. package/vendor/framey/dist/render-batch-ZAMFPCLL.js +274 -0
  548. package/vendor/framey/dist/renderSetupWorker.js +120 -0
  549. package/vendor/framey/dist/resolver-D5BIPSEW.js +42 -0
  550. package/vendor/framey/dist/runtimeVersion.js +18 -0
  551. package/vendor/framey/dist/sam-WFZXI4F7.js +23 -0
  552. package/vendor/framey/dist/shaderTransitionWorker.js +44 -0
  553. package/vendor/framey/dist/sherpa-RQWLV7I3.js +79 -0
  554. package/vendor/framey/dist/sherpaWorker.js +84 -0
  555. package/vendor/framey/dist/sites-AMOVABTK.js +54 -0
  556. package/vendor/framey/dist/skill-ZHVLH7KX.js +15 -0
  557. package/vendor/framey/dist/slideshow-SJGK4RQS.js +24 -0
  558. package/vendor/framey/dist/snapshot-TUXL6LBQ.js +1515 -0
  559. package/vendor/framey/dist/src-JHQGB2S6.js +215 -0
  560. package/vendor/framey/dist/src-Z5PGEMXB.js +532 -0
  561. package/vendor/framey/dist/state-B6H3WHCF.js +28 -0
  562. package/vendor/framey/dist/staticProjectServer-DFVZOUGI.js +63 -0
  563. package/vendor/framey/dist/studio/assets/hyperframes-player-DLaKjw0L.js +478 -0
  564. package/vendor/framey/dist/studio/assets/index-BX3KHhGX.js +71 -0
  565. package/vendor/framey/dist/studio/assets/index-CYbvOVp-.css +1 -0
  566. package/vendor/framey/dist/studio/assets/index-Dp3QEUGL.js +402 -0
  567. package/vendor/framey/dist/studio/assets/index-qrTAXbqc.js +531 -0
  568. package/vendor/framey/dist/studio/favicon.svg +14 -0
  569. package/vendor/framey/dist/studio/icons/timeline/audio.svg +7 -0
  570. package/vendor/framey/dist/studio/icons/timeline/captions.svg +5 -0
  571. package/vendor/framey/dist/studio/icons/timeline/composition.svg +12 -0
  572. package/vendor/framey/dist/studio/icons/timeline/image.svg +18 -0
  573. package/vendor/framey/dist/studio/icons/timeline/music.svg +10 -0
  574. package/vendor/framey/dist/studio/icons/timeline/text.svg +3 -0
  575. package/vendor/framey/dist/studio/index.html +23 -0
  576. package/vendor/framey/dist/studio-api-2NECGRMB.js +179 -0
  577. package/vendor/framey/dist/studioSelectionClient-IUUPKUHZ.js +25 -0
  578. package/vendor/framey/dist/studioServer-N2OPUMOQ.js +1569 -0
  579. package/vendor/framey/dist/synthesize-PKTB5ATG.js +183 -0
  580. package/vendor/framey/dist/system-7F3UEHQT.js +27 -0
  581. package/vendor/framey/dist/telemetry-DQGPEKM6.js +145 -0
  582. package/vendor/framey/dist/telemetry-UN55N6U6.js +77 -0
  583. package/vendor/framey/dist/timeline-LHUUMK63.js +1295 -0
  584. package/vendor/framey/dist/tokens-OLECWDFK.js +92 -0
  585. package/vendor/framey/dist/transcribe-O7HQKMIV.js +331 -0
  586. package/vendor/framey/dist/transcribe-UJESPBT5.js +76 -0
  587. package/vendor/framey/dist/tts-YCCQHZD4.js +234 -0
  588. package/vendor/framey/dist/urlDownloader-H2LXLG4U.js +48 -0
  589. package/vendor/framey/dist/usage-HG4WMM7A.js +2167 -0
  590. package/vendor/framey/dist/validate-F2XMN6EV.js +87 -0
  591. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/GSAP-LICENSE.txt +8 -0
  592. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/CustomEase.min.js +11 -0
  593. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/MotionPathPlugin.min.js +11 -0
  594. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/gsap.min.js +11 -0
  595. package/vendor/framey/dist/warmCheck.js +220 -0
  596. package/vendor/framey/dist/warmCheckClient.js +139 -0
  597. package/vendor/framey/dist/workspaceBuildCheck-XYD2YTFW.js +133 -0
  598. package/vendor/framey-player/LICENSE +190 -0
  599. package/vendor/framey-player/NOTICE +16 -0
  600. package/vendor/framey-player/SOURCE.json +6 -0
  601. package/vendor/framey-player/hyperframes-player.global.js +478 -0
  602. package/vendor/framey.pin +1 -0
  603. package/bin/index.js +0 -3
@@ -0,0 +1,1515 @@
1
+ import { createRequire as __hf_createRequire } from "node:module";
2
+ import { fileURLToPath as __hf_fileURLToPath } from "node:url";
3
+ import { dirname as __hf_dirname } from "node:path";
4
+ var require = __hf_createRequire(import.meta.url);
5
+ var __filename = __hf_fileURLToPath(import.meta.url);
6
+ var __dirname = __hf_dirname(__filename);
7
+ import {
8
+ writeCaptureFileSync
9
+ } from "./chunk-GKZEVJIB.js";
10
+ import {
11
+ toDomNode
12
+ } from "./chunk-LLMSX6V6.js";
13
+ import {
14
+ parseAngle
15
+ } from "./chunk-KZI44OPA.js";
16
+ import {
17
+ formatLintFindings
18
+ } from "./chunk-JGXJU5PF.js";
19
+ import {
20
+ loadInstalledSharp,
21
+ loadOptionalPackage
22
+ } from "./chunk-VMC4WSH5.js";
23
+ import "./chunk-6BEFEWFB.js";
24
+ import {
25
+ findFFmpeg,
26
+ findFFprobe,
27
+ getFFmpegInstallHint
28
+ } from "./chunk-UMD3V7CU.js";
29
+ import {
30
+ pngDimensions,
31
+ reportArtifacts,
32
+ withProjectLock
33
+ } from "./chunk-3LM6YPBR.js";
34
+ import {
35
+ captureArtifactRevision
36
+ } from "./chunk-UTEIUGAZ.js";
37
+ import "./chunk-IXU6PQSW.js";
38
+ import "./chunk-X7MTZ4T2.js";
39
+ import {
40
+ withMeta
41
+ } from "./chunk-O4YRB27L.js";
42
+ import {
43
+ resolveProject
44
+ } from "./chunk-O7BGSQ5F.js";
45
+ import {
46
+ serveStaticProjectHtml
47
+ } from "./chunk-C5BO7PMT.js";
48
+ import "./chunk-X7MTZ4T2.js";
49
+ import "./chunk-6DDPOHMK.js";
50
+ import "./chunk-XUHOIDKG.js";
51
+ import "./chunk-THJALHFJ.js";
52
+ import "./chunk-IUONB55J.js";
53
+ import "./chunk-6JR2XUEE.js";
54
+ import "./chunk-MI5FTLYK.js";
55
+ import {
56
+ artifactDirectory,
57
+ finishSnapshotRun,
58
+ forgetRecordedArtifacts,
59
+ recordArtifacts,
60
+ retainSnapshotOutput
61
+ } from "./chunk-YPRBGITP.js";
62
+ import "./chunk-7JQFJ6JJ.js";
63
+ import {
64
+ DEFAULT_ZOOM_SCALE,
65
+ SCREENSHOT_IMAGE_WAIT_MS,
66
+ captureRegionCrop,
67
+ openSettledCompositionPage,
68
+ parseZoomTarget,
69
+ resolveCropRegion,
70
+ runFfmpegOnce,
71
+ seekCompositionTimelineOnly,
72
+ settleCompositionSeek
73
+ } from "./chunk-SZNTYTIV.js";
74
+ import "./chunk-YOR4CTCE.js";
75
+ import "./chunk-KXTKKBOC.js";
76
+ import "./chunk-P5ZBLXJ6.js";
77
+ import "./chunk-LEKZNRKI.js";
78
+ import "./chunk-442JNXRM.js";
79
+ import "./chunk-4BNN5FGX.js";
80
+ import "./chunk-XT7X6QCP.js";
81
+ import "./chunk-4YYJOFKS.js";
82
+ import "./chunk-HVW7ABXD.js";
83
+ import "./chunk-DQBY7FRR.js";
84
+ import {
85
+ resolveLocalBrowserGpuMode
86
+ } from "./chunk-WKBM7NDG.js";
87
+ import "./chunk-XX7LTW5Y.js";
88
+ import {
89
+ runWithConcurrencyLimit
90
+ } from "./chunk-JHOCK3DG.js";
91
+ import "./chunk-XPTOJZJF.js";
92
+ import "./chunk-RGLQURIX.js";
93
+ import "./chunk-P6LJ4Y32.js";
94
+ import "./chunk-GMECXFAN.js";
95
+ import "./chunk-FLREUEHW.js";
96
+ import "./chunk-EOVYQ3IG.js";
97
+ import "./chunk-GNEFDLFB.js";
98
+ import "./chunk-2UZQHDM7.js";
99
+ import "./chunk-NYEXNSHM.js";
100
+ import {
101
+ defineCommand
102
+ } from "./chunk-S3S4TGEH.js";
103
+ import "./chunk-54HJ63Q2.js";
104
+ import "./chunk-ISN5GRYI.js";
105
+ import {
106
+ c
107
+ } from "./chunk-VORQIH64.js";
108
+ import {
109
+ CliRuntimeError,
110
+ failCommand
111
+ } from "./chunk-JVBRJMOP.js";
112
+ import "./chunk-KLIKCX6Q.js";
113
+ import "./chunk-EVMY7IPL.js";
114
+ import "./chunk-7FMUTGOH.js";
115
+ import "./chunk-TIQB63MX.js";
116
+ import "./chunk-IZCW2NKT.js";
117
+ import "./chunk-RNV3DM4S.js";
118
+ import {
119
+ createRuntimeStartTimeResolver,
120
+ isClipVisibleAt,
121
+ isInClipWindow
122
+ } from "./chunk-DLA44CMS.js";
123
+ import {
124
+ readElementRateSpec,
125
+ sourceTimeAt,
126
+ timeAtSourceTime
127
+ } from "./chunk-UJZREAV6.js";
128
+ import "./chunk-VC76R6W2.js";
129
+ import "./chunk-X7HBSTCZ.js";
130
+ import {
131
+ topLevelElements,
132
+ trackKindOf
133
+ } from "./chunk-B7RJV74O.js";
134
+ import "./chunk-DPVXBVLL.js";
135
+ import "./chunk-OFQWTKPW.js";
136
+ import "./chunk-634HSJ3J.js";
137
+ import "./chunk-FXMKHHPO.js";
138
+ import "./chunk-RQVDTPGD.js";
139
+ import "./chunk-AHX34EFP.js";
140
+ import {
141
+ runCancellableProcess
142
+ } from "./chunk-BL4U6U52.js";
143
+ import {
144
+ definitiveEntryMismatchComposition,
145
+ hasDefinitiveEntryMismatch
146
+ } from "./chunk-Q5X2B5WT.js";
147
+ import {
148
+ normalizeErrorMessage
149
+ } from "./chunk-T2EUWCJE.js";
150
+ import "./chunk-T6AGUTZJ.js";
151
+ import "./chunk-XT6MXC7A.js";
152
+ import "./chunk-K4MJX7SR.js";
153
+ import "./chunk-4UMPIPCQ.js";
154
+ import {
155
+ lintProject
156
+ } from "./chunk-4P7NX5BM.js";
157
+ import "./chunk-VAS6HQX3.js";
158
+ import "./chunk-6BNTGNGD.js";
159
+ import "./chunk-CZGHFATT.js";
160
+ import "./chunk-PKZ7RK5N.js";
161
+ import "./chunk-W2LGJSEN.js";
162
+ import "./chunk-MMQ52NIZ.js";
163
+ import "./chunk-BSQ7XANM.js";
164
+ import {
165
+ parseHTML
166
+ } from "./chunk-SDYDNHEE.js";
167
+ import "./chunk-RXATE6FZ.js";
168
+ import "./chunk-3422KMTD.js";
169
+ import "./chunk-KGPLIH7B.js";
170
+ import "./chunk-3GXQPCJC.js";
171
+ import "./chunk-M2WBOQ2Q.js";
172
+
173
+ // src/commands/snapshotSampling.ts
174
+ function parseSnapshotRange(value) {
175
+ const parts = value.split(/[,:]/);
176
+ const range = parts.map((part) => part.trim() === "" ? NaN : Number(part));
177
+ if (parts.length !== 2 || !range.every(Number.isFinite) || range[0] < 0 || range[1] <= range[0])
178
+ throw new Error("--range must be START,END in seconds, with 0 <= START < END");
179
+ return [range[0], range[1]];
180
+ }
181
+ function parseSnapshotAt(value) {
182
+ const times = value.split(",").map((part) => part.trim() === "" ? NaN : Number(part));
183
+ if (!times.every((time) => Number.isFinite(time) && time >= 0))
184
+ throw new Error("--at must be comma-separated times in seconds, each 0 or more");
185
+ return times;
186
+ }
187
+ function sampleSnapshotRange([start, end], frames, duration, exclusiveEnd = false) {
188
+ if (!Number.isInteger(frames) || frames < 1)
189
+ throw new Error("--frames must be a positive integer");
190
+ if (!Number.isFinite(duration) || duration <= 0 || !Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end <= start || end > duration)
191
+ throw new Error(`Snapshot range must satisfy 0 <= START < END <= ${duration}s`);
192
+ if (frames === 1) return [(start + end) / 2];
193
+ const last = exclusiveEnd || end === duration ? end - Math.min(1e-3, (end - start) / (frames * 2)) : end;
194
+ return Array.from({ length: frames }, (_, i) => start + (last - start) * i / (frames - 1));
195
+ }
196
+ function snapshotStartResolver(document2, durations) {
197
+ return createRuntimeStartTimeResolver({
198
+ documentRef: document2,
199
+ timelineRegistry: Object.fromEntries(
200
+ Object.entries(durations).map(([key, value]) => [key, { duration: () => value }])
201
+ ),
202
+ includeAuthoredTimingAttrs: true
203
+ });
204
+ }
205
+ function resolveSnapshotCompositionRange(html, durations, id, duration) {
206
+ const { document: document2 } = parseHTML(html);
207
+ const matches = Array.from(document2.querySelectorAll("[data-composition-id]")).filter(
208
+ (el) => el.getAttribute("data-composition-id") === id
209
+ );
210
+ if (matches.length !== 1)
211
+ throw new Error(
212
+ `Composition ID ${JSON.stringify(id)} matched ${matches.length} elements; expected exactly one`
213
+ );
214
+ const resolver = snapshotStartResolver(document2, durations);
215
+ let start = 0, end = duration;
216
+ for (let el = matches[0]; el; el = el.parentElement) {
217
+ if (!el.hasAttribute("data-composition-id") && !el.hasAttribute("data-start")) continue;
218
+ const localStart = resolver.resolveStartForElement(el, 0);
219
+ const length = resolver.resolveDurationForElement(el);
220
+ if (!Number.isFinite(localStart))
221
+ throw new Error(`Cannot resolve start of composition ${JSON.stringify(id)}`);
222
+ start = Math.max(start, localStart);
223
+ if (length != null) end = Math.min(end, localStart + length);
224
+ }
225
+ if (end <= start)
226
+ throw new Error(`Composition ${JSON.stringify(id)} has no visible time interval`);
227
+ return [start, end];
228
+ }
229
+
230
+ // src/commands/snapshot.ts
231
+ import { randomUUID } from "crypto";
232
+ import { existsSync, mkdtempSync, readFileSync, mkdirSync, rmSync, writeFileSync } from "fs";
233
+ import { tmpdir } from "os";
234
+ import { resolve, join, relative, isAbsolute, basename, posix } from "path";
235
+
236
+ // src/commands/snapshotReview.ts
237
+ var REVIEW_SETTLE_SEC = 0.3;
238
+ var REVIEW_MID_GAP_SEC = 0.5;
239
+ var REVIEW_SEAM_OFFSETS = [-0.2, -0.1, 0, 0.1, 0.2];
240
+ var REVIEW_PHONE_WIDTH = 360;
241
+ var SAME_TIME_SEC = 0.05;
242
+ var EVEN_BEATS = 5;
243
+ function roundMs(value) {
244
+ return Math.round(value * 1e3) / 1e3;
245
+ }
246
+ function sceneId(el, index) {
247
+ return el.id || el.getAttribute("data-composition-id") || el.getAttribute("data-layout-name") || `${el.tagName.toLowerCase()}-${index + 1}`;
248
+ }
249
+ function resolveReviewTimeline(html, durations, duration, compositionId) {
250
+ const bounds = compositionId ? resolveSnapshotCompositionRange(html, durations, compositionId, duration) : [0, duration];
251
+ const { document: document2 } = parseHTML(html);
252
+ const target = compositionId ? Array.from(document2.querySelectorAll("[data-composition-id]")).find(
253
+ (el) => el.getAttribute("data-composition-id") === compositionId
254
+ ) : document2.querySelector("[data-composition-id]");
255
+ if (!target) {
256
+ return { window: bounds, scenes: [], beats: [], notes: ["No composition root found."] };
257
+ }
258
+ const resolver = snapshotStartResolver(document2, durations);
259
+ const span = (el) => {
260
+ const start = resolver.resolveStartForElement(el, 0);
261
+ if (!Number.isFinite(start)) return null;
262
+ const length = resolver.resolveDurationForElement(el);
263
+ const from = Math.max(bounds[0], start);
264
+ const to = Math.min(bounds[1], length != null ? start + length : bounds[1]);
265
+ return to > from ? [roundMs(from), roundMs(to)] : null;
266
+ };
267
+ const scenes = [];
268
+ const rows = topLevelElements(toDomNode(target)).filter((node) => {
269
+ const kind = trackKindOf(node).kind;
270
+ return kind === "video" || kind === "graphics";
271
+ });
272
+ rows.forEach((node, index) => {
273
+ const interval = span(node.el);
274
+ if (interval)
275
+ scenes.push({ id: sceneId(node.el, index), start: interval[0], end: interval[1] });
276
+ });
277
+ const beats = [];
278
+ const notes = [];
279
+ for (const el of Array.from(target.querySelectorAll("[data-beat]"))) {
280
+ const name = el.getAttribute("data-beat")?.trim() || sceneId(el, beats.length);
281
+ const timed = el.hasAttribute("data-start") || el.hasAttribute("data-composition-id");
282
+ const interval = timed ? span(el) : null;
283
+ if (!interval) {
284
+ notes.push(`data-beat="${name}" was skipped: give it a data-start so the beat has a time.`);
285
+ continue;
286
+ }
287
+ beats.push({ time: settledTime(interval[0], interval[1]), label: `beat:${name}` });
288
+ }
289
+ scenes.sort((a, b) => a.start - b.start || a.end - b.end);
290
+ return { window: bounds, scenes, beats, notes };
291
+ }
292
+ function sceneBeats(scene) {
293
+ const settled = settledTime(scene.start, scene.end);
294
+ const middle = roundMs((scene.start + scene.end) / 2);
295
+ const beats = [{ time: settled, label: scene.id }];
296
+ return middle - settled >= REVIEW_MID_GAP_SEC ? [...beats, { time: middle, label: `${scene.id} mid` }] : beats;
297
+ }
298
+ function settledTime(start, end) {
299
+ return roundMs(Math.min(start + REVIEW_SETTLE_SEC, (start + end) / 2));
300
+ }
301
+ function planReview(timeline, lastFrame) {
302
+ const { window: bounds, scenes } = timeline;
303
+ const notes = [...timeline.notes];
304
+ const clamp = (time) => roundMs(Math.min(Math.max(time, bounds[0]), lastFrame));
305
+ const distinctStarts = mergeBeats(
306
+ scenes.map((scene) => ({ time: scene.start, label: scene.id }))
307
+ );
308
+ const beats = mergeBeats([
309
+ ...distinctStarts.length >= 2 ? scenes.flatMap(sceneBeats) : evenBeats(bounds, notes),
310
+ ...timeline.beats
311
+ ]).map((beat) => ({ ...beat, time: clamp(beat.time) }));
312
+ const seams = seamTimes(scenes, bounds).map((time) => ({
313
+ time,
314
+ ...seamSides(scenes, time),
315
+ times: REVIEW_SEAM_OFFSETS.map((offset) => clamp(time + offset))
316
+ }));
317
+ return { window: bounds, scenes, beats, seams, notes };
318
+ }
319
+ function evenBeats(bounds, notes) {
320
+ notes.push(
321
+ `Fewer than two scene starts; ${EVEN_BEATS} beats are spread evenly instead. Time scenes as sub-composition hosts or clips to review each one.`
322
+ );
323
+ const [start, end] = bounds;
324
+ return Array.from({ length: EVEN_BEATS }, (_, index) => {
325
+ const time = roundMs(start + (index + 0.5) / EVEN_BEATS * (end - start));
326
+ return { time, label: `t=${time}` };
327
+ });
328
+ }
329
+ function mergeBeats(beats) {
330
+ const merged = [];
331
+ for (const beat of [...beats].sort((a, b) => a.time - b.time)) {
332
+ const same = merged.find((entry) => Math.abs(entry.time - beat.time) < SAME_TIME_SEC);
333
+ if (same) same.label = `${same.label} + ${beat.label}`;
334
+ else merged.push({ ...beat });
335
+ }
336
+ return merged;
337
+ }
338
+ function seamTimes(scenes, bounds) {
339
+ const inside = (time) => time > bounds[0] + SAME_TIME_SEC && time < bounds[1] - SAME_TIME_SEC;
340
+ const starts = scenes.map((scene) => scene.start).filter(inside);
341
+ const ends = scenes.map((scene) => scene.end).filter(inside).filter((end) => !starts.some((start) => Math.abs(start - end) < SAME_TIME_SEC));
342
+ const seams = [];
343
+ for (const time of [...starts, ...ends].sort((a, b) => a - b)) {
344
+ if (!seams.some((seam) => Math.abs(seam - time) < SAME_TIME_SEC)) seams.push(time);
345
+ }
346
+ return seams;
347
+ }
348
+ function seamSides(scenes, time) {
349
+ const before = scenes.filter((scene) => scene.start < time - SAME_TIME_SEC && scene.end >= time - SAME_TIME_SEC).sort((a, b) => b.start - a.start)[0];
350
+ const after = scenes.filter((scene) => Math.abs(scene.start - time) < SAME_TIME_SEC).sort((a, b) => a.end - b.end)[0];
351
+ const next = after ?? scenes.find((scene) => scene.start > time);
352
+ return { from: before?.id ?? null, to: next?.id ?? null };
353
+ }
354
+ function pad(index) {
355
+ return String(index).padStart(2, "0");
356
+ }
357
+ function stamp(time) {
358
+ return `${Number(time.toFixed(3))}s`;
359
+ }
360
+ var SEAM_TILE_LABELS = ["cut -0.2", "-0.1", "cut", "+0.1", "+0.2"];
361
+ function reviewFrames(plan) {
362
+ const beats = plan.beats.map((beat, index) => ({
363
+ role: "beat",
364
+ index: index + 1,
365
+ time: beat.time,
366
+ label: beat.label,
367
+ file: `beat-${pad(index + 1)}-at-${stamp(beat.time)}.png`
368
+ }));
369
+ const seams = plan.seams.flatMap(
370
+ (seam, index) => seam.times.map((time, slot) => ({
371
+ role: "seam",
372
+ index: index + 1,
373
+ time,
374
+ label: slot === 2 ? `cut ${seam.from ?? "start"} -> ${seam.to ?? "end"}` : SEAM_TILE_LABELS[slot] ?? "",
375
+ file: `seam-${pad(index + 1)}-${slot + 1}-at-${stamp(time)}.png`
376
+ }))
377
+ );
378
+ return [...beats, ...seams];
379
+ }
380
+ var BEAT_SHEET_PAGE = 16;
381
+ async function writeReviewSheets(dir, frames, written) {
382
+ const { createContactSheet } = await import("./contactSheet-7W6GYHBQ.js");
383
+ const { join: join2 } = await import("path");
384
+ const captured = frames.flatMap((frame) => {
385
+ const path = written(frame);
386
+ return path ? [{ frame, path }] : [];
387
+ });
388
+ const beats = captured.filter((entry) => entry.frame.role === "beat");
389
+ const beatLabel = ({ frame }) => `${stamp(frame.time)} ${frame.label}`;
390
+ const pages = async (name, cellWidth, colsFor) => {
391
+ const sheets = [];
392
+ for (let start = 0; start < beats.length; start += BEAT_SHEET_PAGE) {
393
+ const page = beats.slice(start, start + BEAT_SHEET_PAGE);
394
+ const suffix = beats.length > BEAT_SHEET_PAGE ? `-${start / BEAT_SHEET_PAGE + 1}` : "";
395
+ const sheet = await createContactSheet(
396
+ page.map((entry) => entry.path),
397
+ join2(dir, `${name}${suffix}.jpg`),
398
+ {
399
+ cols: colsFor(page.length),
400
+ maxImages: page.length,
401
+ cellWidth,
402
+ labelMode: "custom",
403
+ labels: page.map(beatLabel)
404
+ }
405
+ );
406
+ if (sheet) sheets.push(sheet);
407
+ }
408
+ return sheets;
409
+ };
410
+ const seams = [];
411
+ const seamNumbers = [
412
+ ...new Set(captured.filter((e) => e.frame.role === "seam").map((e) => e.frame.index))
413
+ ];
414
+ for (const index of seamNumbers) {
415
+ const strip = captured.filter((e) => e.frame.role === "seam" && e.frame.index === index);
416
+ const sheet = await createContactSheet(
417
+ strip.map((entry) => entry.path),
418
+ join2(dir, `review-seam-${pad(index)}.jpg`),
419
+ {
420
+ cols: strip.length,
421
+ maxImages: strip.length,
422
+ cellWidth: 384,
423
+ labelMode: "custom",
424
+ labels: strip.map(({ frame }) => `${stamp(frame.time)} ${frame.label}`)
425
+ }
426
+ );
427
+ if (sheet) seams.push(sheet);
428
+ }
429
+ return {
430
+ beats: await pages("review-beats", 480, (count) => count <= 3 ? count : count <= 9 ? 3 : 4),
431
+ seams,
432
+ phone: await pages("review-phone", REVIEW_PHONE_WIDTH, (count) => Math.min(4, count))
433
+ };
434
+ }
435
+
436
+ // src/commands/snapshot.ts
437
+ function orbitStageSource() {
438
+ return `function(cam) {
439
+ var root = document.querySelector("[data-composition-id]")
440
+ || document.querySelector("#stage")
441
+ || document.body.firstElementChild
442
+ || document.body;
443
+ var n = root;
444
+ while (n && n !== document.body) {
445
+ n.style.transformStyle = "preserve-3d";
446
+ n.style.perspective = "none";
447
+ n = n.parentElement;
448
+ }
449
+ root.style.transformStyle = "preserve-3d";
450
+ root.style.perspective = "none";
451
+ root.style.transformOrigin = "50% 50%";
452
+ root.style.transform = "rotateX(" + cam.pitch + "deg) rotateY(" + cam.yaw + "deg)";
453
+ var lens = root.parentElement || document.body;
454
+ lens.style.perspective = "1600px";
455
+ lens.style.perspectiveOrigin = "50% 50%";
456
+ }`;
457
+ }
458
+ var FFMPEG_EXTRACT_TIMEOUT_MS = 3e4;
459
+ var VIDEO_FRAME_EXTRACT_CONCURRENCY = 4;
460
+ function formatSnapshotTimestamp(time) {
461
+ return `${Number(time.toFixed(3))}s`;
462
+ }
463
+ var SNAPSHOT_IMAGE_NAME = /^(?:(?:frame-\d+-|ref-(?:\d+-)?)?at-[\d.]+s\.png|pair-(?:\d+-)?at-[\d.]+s\.jpg|(?:beat-\d+|seam-\d+-\d+)-at-[\d.]+s\.png|(?:contact-sheet|review-beats|review-phone)(?:-\d+)?\.jpg|review-seam-\d+\.jpg)$/;
464
+ function assertDistinctKeepNames(times) {
465
+ const timeByLabel = /* @__PURE__ */ new Map();
466
+ for (const time of times) {
467
+ const label = formatSnapshotTimestamp(time);
468
+ const other = timeByLabel.get(label);
469
+ if (other !== void 0 && other !== time) {
470
+ throw new Error(
471
+ `--keep would name both ${other} and ${time} at-${label}.png; pass times at least 1 ms apart`
472
+ );
473
+ }
474
+ timeByLabel.set(label, time);
475
+ }
476
+ }
477
+ function resolveSnapshotVideoFrameTime(input) {
478
+ const { globalTime, clipStart, clipDuration, sourceDuration, compositionDuration } = input;
479
+ const clipEnd = clipStart + clipDuration;
480
+ if (!isClipVisibleAt(globalTime, clipStart, clipEnd, compositionDuration)) return null;
481
+ const relativeTime = globalTime < clipStart ? Math.max(0, input.relativeTime) : input.relativeTime;
482
+ if (relativeTime < 0) return null;
483
+ const pastSource = sourceDuration > 0 && relativeTime >= sourceDuration;
484
+ if (isInClipWindow(globalTime, clipStart, clipEnd) && !pastSource) return relativeTime;
485
+ const sourceEnd = sourceDuration > 0 ? sourceDuration : relativeTime;
486
+ return Math.max(0, Math.min(relativeTime, sourceEnd - 1 / 30));
487
+ }
488
+ function resolveSnapshotVideoClipStart(input) {
489
+ return input.runtimeResolvedStart ?? input.authoredStart;
490
+ }
491
+ function resolveSnapshotVideoRateSpec(input) {
492
+ const authoredRate = Number.parseFloat(input.authoredRate ?? "");
493
+ const attrs = {
494
+ "data-playback-rate": Number.isFinite(authoredRate) && authoredRate > 0 ? input.authoredRate : String(input.defaultRate),
495
+ "data-automation": input.authoredAutomation
496
+ };
497
+ return readElementRateSpec({ getAttribute: (name) => attrs[name] ?? null });
498
+ }
499
+ function requireSnapshotFfmpeg(ffmpegPath) {
500
+ if (ffmpegPath) return ffmpegPath;
501
+ throw new Error(
502
+ `FFmpeg is required to extract video frames for snapshots. ${getFFmpegInstallHint()}`
503
+ );
504
+ }
505
+ async function recaptureSnapshotComposite(page) {
506
+ const hasPageComposite = await page.evaluate(async () => {
507
+ const runtimeWindow = window;
508
+ if (typeof runtimeWindow.__hf_page_composite_resolve !== "function") return false;
509
+ await runtimeWindow.__hf_page_composite_prepare?.();
510
+ return true;
511
+ });
512
+ if (hasPageComposite) {
513
+ await page.screenshot({
514
+ type: "jpeg",
515
+ quality: 1,
516
+ clip: { x: 0, y: 0, width: 1, height: 1 }
517
+ });
518
+ await page.evaluate(() => {
519
+ const runtimeWindow = window;
520
+ runtimeWindow.__hf_page_composite_resolve?.();
521
+ });
522
+ }
523
+ }
524
+ function containingSourceFrameIndex(timestamps, time) {
525
+ if (!Number.isFinite(time)) return -1;
526
+ const tolerance = 16 * Number.EPSILON * Math.max(1, Math.abs(time));
527
+ let index = -1;
528
+ for (const [i, timestamp] of timestamps.entries()) {
529
+ if (timestamp > time + tolerance) break;
530
+ index = i;
531
+ }
532
+ return index;
533
+ }
534
+ async function probeContainingSourceFrameSeek(videoPath, time, holdLastFrame) {
535
+ const ffprobe = findFFprobe();
536
+ if (!ffprobe) return { seek: time };
537
+ const probe = async (readIntervals, entries) => {
538
+ try {
539
+ const { stdout } = await runCancellableProcess(
540
+ ffprobe,
541
+ [
542
+ ...["-v", "error", "-select_streams", "v:0", "-read_intervals", readIntervals],
543
+ ...["-show_entries", entries, "-of", "json", "--", videoPath]
544
+ ],
545
+ { timeoutMs: FFMPEG_EXTRACT_TIMEOUT_MS, maxBufferBytes: 32 * 1024 * 1024 }
546
+ );
547
+ const parsed = JSON.parse(stdout);
548
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
549
+ } catch {
550
+ return null;
551
+ }
552
+ };
553
+ const data = await probe("%+#1", "stream=time_base,start_pts:format=start_time,duration");
554
+ if (data === null || !Array.isArray(data.streams)) return null;
555
+ const stream = data.streams[0];
556
+ if (typeof stream !== "object" || stream === null || !("time_base" in stream) || typeof stream.time_base !== "string")
557
+ return null;
558
+ const [numerator, denominator] = stream.time_base.split("/");
559
+ const timeBase = Number(numerator) / Number(denominator);
560
+ if (!Number.isFinite(timeBase) || timeBase <= 0) return null;
561
+ const format = "format" in data ? data.format : null;
562
+ const start = typeof format === "object" && format !== null && "start_time" in format ? Number(format.start_time) : 0;
563
+ const duration = typeof format === "object" && format !== null && "duration" in format ? Number(format.duration) : NaN;
564
+ if (!holdLastFrame && Number.isFinite(duration) && time >= duration) return null;
565
+ const formatStart = Number.isFinite(start) ? start : 0;
566
+ const startPts = "start_pts" in stream && typeof stream.start_pts === "number" ? stream.start_pts : NaN;
567
+ const startTicks = Math.abs(startPts * timeBase - formatStart) <= 1e-6 ? startPts : formatStart / timeBase;
568
+ const packetData = await probe(`%${startTicks * timeBase + time + 1}`, "packet=pts,dts,flags");
569
+ if (packetData === null || !Array.isArray(packetData.packets)) return null;
570
+ const packets = [];
571
+ for (const packet of packetData.packets) {
572
+ if (typeof packet !== "object" || packet === null) return null;
573
+ const pts = "pts" in packet ? packet.pts : "dts" in packet ? packet.dts : void 0;
574
+ if (typeof pts !== "number") return null;
575
+ const dts = "dts" in packet && typeof packet.dts === "number" ? packet.dts : pts;
576
+ const key = "flags" in packet && typeof packet.flags === "string" && packet.flags.includes("K");
577
+ packets.push({ pts: (pts - startTicks) * timeBase, dts: (dts - startTicks) * timeBase, key });
578
+ }
579
+ const timestamps = packets.map((packet) => packet.pts).sort((a, b) => a - b);
580
+ const index = Math.max(0, containingSourceFrameIndex(timestamps, time));
581
+ if (timestamps.length === 0 || !Number.isFinite(time)) return null;
582
+ const frame = timestamps[index];
583
+ const previous = timestamps[index - 1] ?? frame - 1e-3;
584
+ const mid = (previous + frame) / 2;
585
+ const ffmpeg = findFFmpeg();
586
+ const landing = async (seek) => {
587
+ const raw = ffmpeg ? await probeSeekLanding(ffmpeg, videoPath, seek) : null;
588
+ return raw === null ? null : raw - startTicks * timeBase;
589
+ };
590
+ const candidates = packets.filter(
591
+ (packet) => packet.key && packet.pts > 0 && packet.pts <= frame
592
+ );
593
+ for (const candidate of candidates.reverse().slice(0, 3)) {
594
+ const landed = await landing(candidate.pts);
595
+ if (landed === null) break;
596
+ if (landed <= 0 || landed > frame) continue;
597
+ if (landed < candidate.pts - 1e-6) return { seek: candidate.pts, select: mid - candidate.pts };
598
+ const again = Math.abs(landed - candidate.pts) < 1e-6 ? landed : await landing(landed);
599
+ if (again !== null && Math.abs(again - landed) < 1e-6)
600
+ return { seek: landed, select: mid - landed };
601
+ }
602
+ return { select: mid - (timestamps.find((t) => t >= 0) ?? frame) };
603
+ }
604
+ async function probeSeekLanding(ffmpeg, videoPath, seek) {
605
+ try {
606
+ const { stdout } = await runCancellableProcess(
607
+ ffmpeg,
608
+ [
609
+ ...["-v", "error", "-copyts", "-noaccurate_seek", "-ss", String(seek), "-i", videoPath],
610
+ ...["-map", "0:v:0", "-c", "copy", "-frames:v", "1", "-f", "framecrc", "-"]
611
+ ],
612
+ { timeoutMs: FFMPEG_EXTRACT_TIMEOUT_MS, maxBufferBytes: 1024 * 1024 }
613
+ );
614
+ const base = /^#tb 0: (\d+)\/(\d+)/m.exec(stdout);
615
+ const row = /^0,\s*-?\d+,\s*(-?\d+),/m.exec(stdout);
616
+ if (!base || !row) return null;
617
+ return Number(row[1]) * Number(base[1]) / Number(base[2]);
618
+ } catch {
619
+ return null;
620
+ }
621
+ }
622
+ async function extractVideoFrameToBuffer(videoPath, timeSeconds, useVp9AlphaDecoder = false, holdLastFrame = false, displayedPixels = 0) {
623
+ const tmp = mkdtempSync(join(tmpdir(), "hf-snapshot-frame-"));
624
+ const outPath = join(tmp, "frame.png");
625
+ try {
626
+ const ffmpegPath = requireSnapshotFfmpeg(findFFmpeg());
627
+ const seek = await probeContainingSourceFrameSeek(
628
+ videoPath,
629
+ Math.max(0, timeSeconds),
630
+ holdLastFrame
631
+ );
632
+ if (seek === null) return null;
633
+ const args = ["-hide_banner", "-loglevel", "error"];
634
+ if (useVp9AlphaDecoder) args.push("-c:v", "libvpx-vp9");
635
+ if (seek.seek !== void 0) args.push("-ss", String(seek.seek));
636
+ args.push("-i", videoPath, "-map", "0:v:0");
637
+ const filters = [];
638
+ if (seek.select !== void 0) filters.push(`select=gte(t-start_t\\,${seek.select})`);
639
+ if (displayedPixels > 0) {
640
+ filters.push(
641
+ `scale='min(iw,${displayedPixels})':'min(ih,${displayedPixels})':force_original_aspect_ratio=increase`
642
+ );
643
+ }
644
+ if (filters.length > 0) args.push("-vf", filters.join(","));
645
+ args.push("-frames:v", "1", "-q:v", "2", "-y", outPath);
646
+ const result = await runFfmpegOnce(ffmpegPath, args, FFMPEG_EXTRACT_TIMEOUT_MS);
647
+ if (result.code !== 0 || result.timedOut || !existsSync(outPath)) return null;
648
+ return readFileSync(outPath);
649
+ } finally {
650
+ try {
651
+ rmSync(tmp, { recursive: true, force: true });
652
+ } catch {
653
+ }
654
+ }
655
+ }
656
+ var examples = [
657
+ ["Capture 5 key frames from a composition", "snapshot capture"],
658
+ ["Capture 10 evenly-spaced frames", "snapshot capture --frames 10"],
659
+ ["View the 3D stage from an isometric angle", "snapshot capture --angle iso"],
660
+ ["Zoom into an element for a high-density crop", "snapshot --zoom '#headline'"],
661
+ [
662
+ "Zoom into an exact pixel region at 2x density",
663
+ "snapshot --zoom 100,50,400,300 --zoom-scale 2"
664
+ ],
665
+ [
666
+ "Pair each frame with the reference footage at the same time",
667
+ "snapshot --at 1.5,4.3,8.1 --against ref.mp4"
668
+ ]
669
+ ];
670
+ function parseZoomScale(value) {
671
+ const parsed = parseFloat(String(value ?? ""));
672
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ZOOM_SCALE;
673
+ }
674
+ function tailFrameTime(duration) {
675
+ return Math.max(0, duration - Math.max(0.05, duration * 0.03));
676
+ }
677
+ function computeSnapshotTimes(duration, opts) {
678
+ const includeEnd = opts.includeEnd !== false;
679
+ const tail = tailFrameTime(duration);
680
+ const round = (t) => Math.round(t * 1e3) / 1e3;
681
+ if (opts.at?.length) {
682
+ const times2 = [...opts.at];
683
+ const hasTail = times2.some((t) => Math.abs(t - tail) < 0.05 || t >= duration);
684
+ if (includeEnd && duration > 0 && !hasTail) {
685
+ return { times: [...times2, round(tail)], appendedTail: true };
686
+ }
687
+ return { times: times2, appendedTail: false };
688
+ }
689
+ const n = opts.frames;
690
+ if (n <= 1) return { times: [round(duration / 2)], appendedTail: false };
691
+ const times = Array.from({ length: n }, (_, i) => i / (n - 1) * duration);
692
+ if (includeEnd) times[times.length - 1] = tail;
693
+ return { times: times.map(round), appendedTail: false };
694
+ }
695
+ function serveCapturePage(projectDir, html, proxy) {
696
+ return serveStaticProjectHtml(projectDir, html, void 0, [], proxy === true);
697
+ }
698
+ async function captureSnapshots(projectDir, opts) {
699
+ const log = opts.log ?? ((message) => console.log(message));
700
+ const { bundleWithLocalizedFonts } = await import("./bundleWithLocalizedFonts-ALYBQEQT.js");
701
+ const capture = await captureArtifactRevision(projectDir).catch(warnSnapshotsNotRecorded);
702
+ const invocation = randomUUID();
703
+ const artifacts = [];
704
+ const replaced = [];
705
+ const warnings = [];
706
+ let completed = false;
707
+ const numFrames = opts.frames ?? 5;
708
+ let review;
709
+ let reviewList;
710
+ const reviewPaths = /* @__PURE__ */ new Map();
711
+ const html = await bundleWithLocalizedFonts(projectDir);
712
+ const server = await serveCapturePage(projectDir, html, opts.autoProxy);
713
+ const savedPaths = [];
714
+ try {
715
+ const {
716
+ browser: chromeBrowser,
717
+ page,
718
+ renderReadyTimedOut
719
+ } = await openSettledCompositionPage(html, server.url, {
720
+ renderReadyTimeoutMs: opts.timeout ?? 5e3,
721
+ renderReadyWarningSuffix: "snapshots may be inaccurate",
722
+ browserGpuMode: opts.browserGpuMode
723
+ });
724
+ try {
725
+ if (renderReadyTimedOut) warnings.push("Render readiness timed out");
726
+ const fontReport = await page.evaluate(() => {
727
+ const loaded = [];
728
+ const errored = [];
729
+ const unused = [];
730
+ document.fonts.forEach((f) => {
731
+ const entry = `${f.family} (${f.weight} ${f.style})`;
732
+ if (f.status === "loaded") loaded.push(entry);
733
+ else if (f.status === "error") errored.push(entry);
734
+ else unused.push(entry);
735
+ });
736
+ return { loaded, errored, unused };
737
+ }).catch(() => ({ loaded: [], errored: [], unused: [] }));
738
+ if (fontReport.loaded.length > 0 || fontReport.errored.length > 0 || fontReport.unused.length > 0) {
739
+ const parts = [`${fontReport.loaded.length} loaded`];
740
+ if (fontReport.errored.length > 0) parts.push(`${fontReport.errored.length} failed`);
741
+ if (fontReport.unused.length > 0) parts.push(`${fontReport.unused.length} unused`);
742
+ log(`
743
+ ${c.dim("Fonts:")} ${parts.join(", ")}`);
744
+ if (fontReport.errored.length > 0) {
745
+ log(` ${c.error("Fonts FAILED:")} ${fontReport.errored.join(", ")}`);
746
+ }
747
+ }
748
+ if (fontReport.errored.length)
749
+ warnings.push(`Fonts failed: ${fontReport.errored.join(", ")}`);
750
+ const duration = await page.evaluate(() => {
751
+ const win = window;
752
+ if (typeof win.__player?.getDuration === "function") {
753
+ const d = win.__player.getDuration();
754
+ if (Number.isFinite(d) && d > 0) return d;
755
+ }
756
+ const root = document.querySelector("[data-composition-id][data-duration]");
757
+ if (root) return parseFloat(root.getAttribute("data-duration") ?? "0");
758
+ return 0;
759
+ });
760
+ if (duration <= 0 && !opts.at?.length) {
761
+ return { paths: [], warnings };
762
+ }
763
+ const readTiming = () => page.evaluate(() => ({
764
+ html: document.documentElement.outerHTML,
765
+ durations: Object.fromEntries(
766
+ Object.entries(window.__timelines ?? {}).map(([id, timeline]) => [
767
+ id,
768
+ Number(timeline.duration?.())
769
+ ])
770
+ )
771
+ }));
772
+ let range = opts.range;
773
+ if (opts.composition && !opts.review) {
774
+ const timing = await readTiming();
775
+ range = resolveSnapshotCompositionRange(
776
+ timing.html,
777
+ timing.durations,
778
+ opts.composition,
779
+ duration
780
+ );
781
+ }
782
+ if (opts.review) {
783
+ const timing = await readTiming();
784
+ const timeline = resolveReviewTimeline(
785
+ timing.html,
786
+ timing.durations,
787
+ duration,
788
+ opts.composition
789
+ );
790
+ review = planReview(timeline, Math.min(timeline.window[1], duration) - 1 / 30);
791
+ reviewList = reviewFrames(review);
792
+ }
793
+ const { times: positions, appendedTail } = reviewList ? { times: reviewList.map((frame) => frame.time), appendedTail: false } : range ? {
794
+ times: sampleSnapshotRange(range, numFrames, duration, !!opts.composition),
795
+ appendedTail: false
796
+ } : computeSnapshotTimes(duration, {
797
+ frames: numFrames,
798
+ at: opts.at,
799
+ includeEnd: opts.includeEnd
800
+ });
801
+ if (range)
802
+ log(
803
+ ` Sampling ${positions.length} frames within ${range[0]}\u2013${range[1]}s${opts.composition ? ` (composition ${opts.composition})` : ""}`
804
+ );
805
+ if (review)
806
+ log(
807
+ ` Review: ${review.beats.length} beat(s) and ${review.seams.length} seam(s) within ${review.window[0]}\u2013${review.window[1]}s${opts.composition ? ` (composition ${opts.composition})` : ""}`
808
+ );
809
+ if (appendedTail) {
810
+ log(
811
+ ` ${c.dim(`Note: added an end-of-timeline frame at ${positions[positions.length - 1].toFixed(2)}s. Short beats between your --at times may still be skipped \u2014 pass them explicitly.`)}`
812
+ );
813
+ }
814
+ const cameraExpr = opts.angle && (opts.angle.yaw !== 0 || opts.angle.pitch !== 0) ? `(${orbitStageSource()})(${JSON.stringify(opts.angle)})` : null;
815
+ const snapshotDir = opts.outputDir ?? artifactDirectory(projectDir, "snapshots");
816
+ mkdirSync(snapshotDir, { recursive: true });
817
+ try {
818
+ const { readdirSync } = await import("fs");
819
+ for (const file of opts.keep ? [] : readdirSync(snapshotDir)) {
820
+ if (SNAPSHOT_IMAGE_NAME.test(file)) {
821
+ const path = resolve(snapshotDir, file);
822
+ replaced.push(path);
823
+ rmSync(path, { force: true });
824
+ }
825
+ }
826
+ } catch {
827
+ }
828
+ let injectVideoFramesBatch = null;
829
+ let syncVideoFrameVisibility = null;
830
+ let extractMediaMetadata = null;
831
+ try {
832
+ const engine = await import("./src-Z5PGEMXB.js");
833
+ injectVideoFramesBatch = engine.injectVideoFramesBatch;
834
+ syncVideoFrameVisibility = engine.syncVideoFrameVisibility;
835
+ extractMediaMetadata = engine.extractMediaMetadata;
836
+ } catch {
837
+ warnings.push("Video frame extraction engine unavailable");
838
+ console.warn(
839
+ ` ${c.warn("\u26A0")} @framey/engine unavailable \u2014 <video> elements will appear black in snapshots. Verify media via a draft render's extracted frames instead.`
840
+ );
841
+ }
842
+ const alphaDecoderCache = /* @__PURE__ */ new Map();
843
+ const shouldUseVp9AlphaDecoder = (filePath) => {
844
+ if (!extractMediaMetadata) return Promise.resolve(false);
845
+ const cached = alphaDecoderCache.get(filePath);
846
+ if (cached) return cached;
847
+ const pending = extractMediaMetadata(filePath).then((meta) => meta.hasAlpha && meta.videoCodec === "vp9").catch(() => false);
848
+ alphaDecoderCache.set(filePath, pending);
849
+ return pending;
850
+ };
851
+ const hasPlayer = await page.evaluate(() => !!window.__player);
852
+ if (!hasPlayer) {
853
+ console.warn(` ${c.warn("\u26A0")} No player API \u2014 seeks will be skipped`);
854
+ }
855
+ for (let i = 0; i < positions.length; i++) {
856
+ const time = positions[i];
857
+ const seekOptions = { exactTime: true, waitForImagesMs: SCREENSHOT_IMAGE_WAIT_MS };
858
+ await seekCompositionTimelineOnly(page, time, seekOptions);
859
+ if (cameraExpr) await page.evaluate(cameraExpr);
860
+ if (injectVideoFramesBatch && syncVideoFrameVisibility) {
861
+ const candidates = await page.evaluate(() => {
862
+ const runtimeWindow = window;
863
+ return Array.from(document.querySelectorAll("video")).map((el) => {
864
+ const v = el;
865
+ const box = v.getBoundingClientRect();
866
+ const authoredStart = parseFloat(v.dataset.start ?? "0") || 0;
867
+ const runtimeResolvedStart = runtimeWindow.__hfResolveMediaStartSeconds?.(v);
868
+ const mediaStart = parseFloat(v.dataset.playbackStart ?? v.dataset.mediaStart ?? "0") || 0;
869
+ const rawDuration = parseFloat(v.dataset.duration ?? "");
870
+ const srcDur = Number.isFinite(v.duration) && v.duration > 0 ? v.duration : 0;
871
+ return {
872
+ id: runtimeWindow.__hfMediaId?.(v) || v.id,
873
+ src: v.currentSrc || v.src,
874
+ authoredStart,
875
+ authoredRate: v.dataset.playbackRate,
876
+ authoredAutomation: v.dataset.automation,
877
+ defaultRate: v.defaultPlaybackRate,
878
+ runtimeResolvedStart: runtimeResolvedStart !== void 0 && Number.isFinite(runtimeResolvedStart) ? runtimeResolvedStart : null,
879
+ authoredDuration: Number.isFinite(rawDuration) && rawDuration > 0 ? rawDuration : null,
880
+ srcDuration: srcDur,
881
+ mediaStart,
882
+ loop: v.loop,
883
+ displayedSize: Math.max(box.width, box.height),
884
+ devicePixelRatio: window.devicePixelRatio
885
+ };
886
+ });
887
+ });
888
+ const compositionDuration = duration;
889
+ const active = candidates.flatMap((candidate) => {
890
+ const start = resolveSnapshotVideoClipStart(candidate);
891
+ const playbackRate = resolveSnapshotVideoRateSpec(candidate);
892
+ const duration2 = candidate.authoredDuration ?? (candidate.srcDuration > 0 ? Math.max(
893
+ 0,
894
+ timeAtSourceTime(playbackRate, candidate.srcDuration - candidate.mediaStart)
895
+ ) : Number.POSITIVE_INFINITY);
896
+ let relTime = sourceTimeAt(playbackRate, time - start) + candidate.mediaStart;
897
+ if (candidate.loop && candidate.srcDuration > candidate.mediaStart && relTime >= candidate.srcDuration) {
898
+ relTime = candidate.mediaStart + (relTime - candidate.mediaStart) % (candidate.srcDuration - candidate.mediaStart);
899
+ }
900
+ if (!candidate.id || !candidate.src) return [];
901
+ const frameTime = resolveSnapshotVideoFrameTime({
902
+ globalTime: time,
903
+ clipStart: start,
904
+ clipDuration: duration2,
905
+ relativeTime: relTime,
906
+ sourceDuration: candidate.srcDuration,
907
+ compositionDuration
908
+ });
909
+ return frameTime === null ? [] : [{ ...candidate, start, playbackRate, duration: duration2, relTime: frameTime }];
910
+ });
911
+ const extracted = await runWithConcurrencyLimit(
912
+ active,
913
+ VIDEO_FRAME_EXTRACT_CONCURRENCY,
914
+ async (v) => {
915
+ let ffmpegInput = null;
916
+ let inputIsLocal = false;
917
+ try {
918
+ const url = new URL(v.src);
919
+ const decodedPath = decodeURIComponent(url.pathname).replace(/^\//, "");
920
+ const candidate = resolve(projectDir, decodedPath);
921
+ const rel2 = relative(projectDir, candidate);
922
+ if (!rel2.startsWith("..") && !isAbsolute(rel2) && existsSync(candidate)) {
923
+ ffmpegInput = candidate;
924
+ inputIsLocal = true;
925
+ } else if (url.protocol === "http:" || url.protocol === "https:") {
926
+ ffmpegInput = url.href;
927
+ }
928
+ } catch {
929
+ }
930
+ if (!ffmpegInput) return null;
931
+ const useVp9AlphaDecoder = inputIsLocal ? await shouldUseVp9AlphaDecoder(ffmpegInput) : false;
932
+ const png = await extractVideoFrameToBuffer(
933
+ ffmpegInput,
934
+ Math.max(0, v.relTime),
935
+ useVp9AlphaDecoder,
936
+ v.srcDuration > 0 && v.relTime >= v.srcDuration - 1,
937
+ Math.ceil(
938
+ v.displayedSize * (opts.zoom ? opts.zoomScale ?? DEFAULT_ZOOM_SCALE : v.devicePixelRatio)
939
+ )
940
+ );
941
+ if (!png) return null;
942
+ return { videoId: v.id, dataUri: `data:image/png;base64,${png.toString("base64")}` };
943
+ }
944
+ );
945
+ const updates = extracted.filter((update) => update !== null);
946
+ if (active.length > 0 && updates.length < active.length) {
947
+ const missed = active.length - updates.length;
948
+ warnings.push(`${missed} video frames missing at ${time}s`);
949
+ console.warn(
950
+ ` ${c.warn("\u26A0")} ${missed}/${active.length} active <video> frame(s) could not be extracted at ${time.toFixed(1)}s \u2014 those videos will appear black/stale in this snapshot`
951
+ );
952
+ }
953
+ try {
954
+ if (updates.length > 0) {
955
+ await injectVideoFramesBatch(page, updates);
956
+ }
957
+ await syncVideoFrameVisibility(
958
+ page,
959
+ active.map((a) => a.id)
960
+ );
961
+ } catch {
962
+ warnings.push(`Video injection failed at ${time}s`);
963
+ console.warn(
964
+ ` ${c.warn("\u26A0")} video frame injection failed at ${time.toFixed(1)}s \u2014 <video> elements will appear black/stale in this snapshot`
965
+ );
966
+ }
967
+ }
968
+ warnUnsettledImages(await settleCompositionSeek(page, seekOptions), time, warnings);
969
+ await recaptureSnapshotComposite(page);
970
+ const timeLabel = formatSnapshotTimestamp(time);
971
+ const slot = opts.keep ? `at-${timeLabel}` : `${String(i).padStart(2, "0")}-at-${timeLabel}`;
972
+ const reviewFrame = reviewList?.[i];
973
+ const filename = reviewFrame ? reviewFrame.file : opts.keep ? `${slot}.png` : `frame-${slot}.png`;
974
+ const framePath = join(snapshotDir, filename);
975
+ let crop;
976
+ if (opts.zoom) {
977
+ const canvas = await page.evaluate(() => ({
978
+ width: window.innerWidth,
979
+ height: window.innerHeight
980
+ }));
981
+ const region = await resolveCropRegion(page, opts.zoom, canvas);
982
+ if (!region) {
983
+ warnings.push(`Crop target unavailable at ${time}s`);
984
+ console.error(
985
+ ` ${c.warn("\u26A0")} --zoom target has no visible box at ${time.toFixed(1)}s \u2014 frame skipped`
986
+ );
987
+ continue;
988
+ }
989
+ crop = region;
990
+ const buffer = await captureRegionCrop(
991
+ page,
992
+ region,
993
+ opts.zoomScale ?? DEFAULT_ZOOM_SCALE
994
+ );
995
+ writeCaptureFileSync(framePath, buffer);
996
+ } else {
997
+ writeCaptureFileSync(
998
+ framePath,
999
+ await page.screenshot({ type: "png", omitBackground: true })
1000
+ );
1001
+ }
1002
+ artifacts.push({
1003
+ id: `${invocation}-${i}`,
1004
+ image: resolve(framePath),
1005
+ time,
1006
+ metadata: {
1007
+ ...pngDimensions(framePath),
1008
+ crop,
1009
+ zoom: opts.zoom,
1010
+ zoomScale: opts.zoom ? opts.zoomScale ?? DEFAULT_ZOOM_SCALE : 1,
1011
+ angle: opts.angle,
1012
+ browserGpuMode: opts.browserGpuMode,
1013
+ ...reviewFrame ? { review: { role: reviewFrame.role, label: reviewFrame.label } } : {}
1014
+ }
1015
+ });
1016
+ if (reviewFrame) reviewPaths.set(reviewFrame, framePath);
1017
+ if (opts.against) {
1018
+ const refPng = await extractVideoFrameToBuffer(opts.against, time);
1019
+ if (!refPng) {
1020
+ console.error(
1021
+ ` ${c.warn("\u26A0")} --against has no frame at ${timeLabel} \u2014 reference pair skipped`
1022
+ );
1023
+ } else {
1024
+ const refPath = join(snapshotDir, `ref-${slot}.png`);
1025
+ writeCaptureFileSync(refPath, refPng);
1026
+ const pairPath = join(snapshotDir, `pair-${slot}.jpg`);
1027
+ const { createContactSheet } = await import("./contactSheet-7W6GYHBQ.js");
1028
+ await createContactSheet([framePath, refPath], pairPath, {
1029
+ cols: 2,
1030
+ maxImages: 2,
1031
+ cellWidth: 960,
1032
+ labelMode: "custom",
1033
+ labels: ["render", "reference"]
1034
+ });
1035
+ }
1036
+ }
1037
+ const rel = relative(projectDir, framePath);
1038
+ savedPaths.push(rel.startsWith("..") || isAbsolute(rel) ? framePath : rel);
1039
+ }
1040
+ completed = true;
1041
+ } finally {
1042
+ await chromeBrowser.close();
1043
+ }
1044
+ } finally {
1045
+ await server.close();
1046
+ const recorded = capture ? await recordSnapshotArtifacts(
1047
+ projectDir,
1048
+ capture,
1049
+ artifacts,
1050
+ opts.zoom,
1051
+ warnings,
1052
+ completed,
1053
+ log
1054
+ ) : false;
1055
+ if (!recorded) {
1056
+ const paths = [...replaced, ...artifacts.map((artifact) => artifact.image)];
1057
+ if (paths.length > 0) await forgetRecordedArtifacts(projectDir, paths).catch(() => void 0);
1058
+ }
1059
+ }
1060
+ return {
1061
+ paths: savedPaths,
1062
+ warnings,
1063
+ ...review && reviewList ? {
1064
+ review: {
1065
+ plan: review,
1066
+ frames: reviewList.flatMap((frame) => {
1067
+ const path = reviewPaths.get(frame);
1068
+ return path ? [{ ...frame, path }] : [];
1069
+ })
1070
+ }
1071
+ } : {}
1072
+ };
1073
+ }
1074
+ function warnUnsettledImages(images, time, warnings) {
1075
+ const groups = [
1076
+ [images.pending, `did not finish loading within ${SCREENSHOT_IMAGE_WAIT_MS} ms`],
1077
+ [images.failed, "failed to load"]
1078
+ ];
1079
+ for (const [sources, what] of groups) {
1080
+ if (sources.length === 0) continue;
1081
+ const message = `${sources.length} image(s) ${what} at ${time}s, so the frame may be missing them: ${sources.join(", ")}`;
1082
+ warnings.push(message);
1083
+ console.warn(` ${c.warn("\u26A0")} ${message}`);
1084
+ }
1085
+ }
1086
+ function warnSnapshotsNotRecorded(error) {
1087
+ console.warn(` ${c.warn("\u26A0")} Snapshots were not recorded: ${normalizeErrorMessage(error)}`);
1088
+ return void 0;
1089
+ }
1090
+ async function recordSnapshotArtifacts(projectDir, capture, artifacts, zoom, warnings, completed, log) {
1091
+ const records = await recordArtifacts(
1092
+ projectDir,
1093
+ capture,
1094
+ artifacts.map((artifact) => ({
1095
+ kind: "snapshot",
1096
+ scope: zoom ? "crop" : "full",
1097
+ ...artifact,
1098
+ metadata: { ...artifact.metadata, warnings, partial: !completed || warnings.length > 0 }
1099
+ }))
1100
+ ).catch(warnSnapshotsNotRecorded);
1101
+ const record = records?.[0];
1102
+ if (!record) return false;
1103
+ log(` Revision: ${record.revision ?? "none"} (${record.attribution})`);
1104
+ log(` State: ${join(projectDir, ".hyperframes", "video-state.json")}`);
1105
+ return true;
1106
+ }
1107
+ function snapshotEnvelope(projectDir, outputDir, captured, extra) {
1108
+ const review = captured.review;
1109
+ const frames = review ? review.frames.map((frame) => ({
1110
+ path: frame.path,
1111
+ time: frame.time,
1112
+ role: frame.role,
1113
+ label: frame.label
1114
+ })) : captured.paths.map((path) => ({ path: resolve(projectDir, path), time: timeFromName(path) }));
1115
+ return {
1116
+ ok: true,
1117
+ outputDir,
1118
+ frames,
1119
+ contactSheets: extra.contactSheets.map((sheet) => resolve(sheet)),
1120
+ ...captured.warnings.length > 0 ? { warnings: captured.warnings } : {},
1121
+ ...extra.describedPath ? { descriptions: extra.describedPath } : {},
1122
+ ...review ? {
1123
+ review: {
1124
+ window: review.plan.window,
1125
+ scenes: review.plan.scenes,
1126
+ beats: review.plan.beats.map((beat, index) => ({
1127
+ ...beat,
1128
+ path: review.frames.find(
1129
+ (frame) => frame.role === "beat" && frame.index === index + 1
1130
+ )?.path
1131
+ })),
1132
+ seams: review.plan.seams.map((seam, index) => ({
1133
+ ...seam,
1134
+ paths: review.frames.filter((frame) => frame.role === "seam" && frame.index === index + 1).map((frame) => frame.path),
1135
+ sheet: extra.reviewSheets?.seams[index]
1136
+ })),
1137
+ sheets: extra.reviewSheets ?? { beats: [], seams: [], phone: [] },
1138
+ notes: review.plan.notes
1139
+ }
1140
+ } : {}
1141
+ };
1142
+ }
1143
+ function timeFromName(path) {
1144
+ const match = /at-([\d.]+)s\.png$/.exec(path);
1145
+ return match ? Number(match[1]) : null;
1146
+ }
1147
+ function printReview(review, sheets, log) {
1148
+ const beats = review.plan.beats.map(
1149
+ (beat) => `${formatSnapshotTimestamp(beat.time)} ${beat.label}`
1150
+ );
1151
+ log(` Beat sheet (settled frame per scene): ${sheets.beats.join(", ") || "none"}`);
1152
+ log(` ${c.dim(beats.join(" \xB7 "))}`);
1153
+ review.plan.seams.forEach((seam, index) => {
1154
+ const sides = `${seam.from ?? "start"} -> ${seam.to ?? "end"}`;
1155
+ const strip = sheets.seams[index] ?? "not written";
1156
+ log(` Seam ${index + 1} at ${formatSnapshotTimestamp(seam.time)} (${sides}): ${strip}`);
1157
+ });
1158
+ log(
1159
+ ` Phone sheet (beats at ${REVIEW_PHONE_WIDTH} px wide): ${sheets.phone.join(", ") || "none"}`
1160
+ );
1161
+ for (const note of review.plan.notes) log(` ${c.dim(note)}`);
1162
+ }
1163
+ var snapshot_default = defineCommand({
1164
+ meta: {
1165
+ name: "snapshot",
1166
+ description: "Capture key frames from a composition as PNG screenshots for visual verification"
1167
+ },
1168
+ args: {
1169
+ dir: {
1170
+ type: "positional",
1171
+ description: "Project directory",
1172
+ required: false
1173
+ },
1174
+ output: {
1175
+ type: "string",
1176
+ alias: "o",
1177
+ description: "Directory to write snapshots into (default: unique directory under <project>/.hyperframes/artifacts/snapshots)"
1178
+ },
1179
+ frames: {
1180
+ type: "string",
1181
+ description: "Number of evenly-spaced frames to capture (default: 5)",
1182
+ default: "5"
1183
+ },
1184
+ at: {
1185
+ type: "string",
1186
+ description: "Comma-separated timestamps in seconds (e.g., --at 3.0,10.5,18.0). Each is captured at that exact instant, not snapped to a frame."
1187
+ },
1188
+ range: {
1189
+ type: "string",
1190
+ description: "Root-timeline range START,END in seconds; sample --frames evenly within it, without adding the full-video tail"
1191
+ },
1192
+ composition: {
1193
+ type: "string",
1194
+ description: "data-composition-id to sample; take --frames within its effective root-timeline interval, without adding the full-video tail"
1195
+ },
1196
+ timeout: {
1197
+ type: "string",
1198
+ description: "Ms to wait for runtime to initialize (default: 5000)",
1199
+ default: "5000"
1200
+ },
1201
+ angle: {
1202
+ type: "string",
1203
+ description: "Orthogonal 3D camera for depth/occlusion checks: a preset (front|iso|top|side) or 'yaw,pitch' degrees. Tilts the whole stage before screenshotting (real pixels, not bbox markers)."
1204
+ },
1205
+ end: {
1206
+ type: "boolean",
1207
+ description: "Always include a readable end-of-timeline frame (default: true). Pass --no-end to capture only your exact --at times.",
1208
+ default: true
1209
+ },
1210
+ keep: {
1211
+ type: "boolean",
1212
+ description: "Add to --output instead of clearing it, naming each still at-<seconds>s.png (at-0s, at-1.5s, at-12.25s: no trailing zeros), so snapshotting one moment again replaces only that still. Times less than 1 ms apart share a name, so one run may not pass two of them. No contact sheet.",
1213
+ default: false
1214
+ },
1215
+ zoom: {
1216
+ type: "string",
1217
+ description: "Zoom into a CSS selector or an exact pixel region 'x,y,w,h'. Crops a high-density screenshot instead of the full frame \u2014 a raised deviceScaleFactor, never CSS zoom or viewport resizing, so layout stays identical. A selector matching nothing is an error, not a silent full-frame shot."
1218
+ },
1219
+ "zoom-scale": {
1220
+ type: "string",
1221
+ description: "Device-scale-factor density for --zoom crops (default: 3)",
1222
+ default: "3"
1223
+ },
1224
+ against: {
1225
+ type: "string",
1226
+ description: "Reference video (e.g. footage being rebuilt): also save its frame at each captured time and a render|reference pair sheet"
1227
+ },
1228
+ review: {
1229
+ type: "boolean",
1230
+ description: "Capture the review set instead of evenly spaced frames: each scene at its start + 0.3 s and its middle, and each data-beat at its start + 0.3 s, on review-beats.jpg, a 5-frame strip around every cut (review-seam-NN.jpg), and the beat frames at phone width (review-phone.jpg). Scopes to --composition; ignores --frames.",
1231
+ default: false
1232
+ },
1233
+ json: {
1234
+ type: "boolean",
1235
+ description: "Print one JSON envelope (paths, times, sheets; the review plan under --review)",
1236
+ default: false
1237
+ },
1238
+ describe: {
1239
+ type: "string",
1240
+ description: "Gemini vision frame analysis. Runs by default when GEMINI_API_KEY is set. Pass a custom question (e.g. --describe 'Is the logo visible in every beat?') to override the default prompt, or --describe false to opt out."
1241
+ },
1242
+ proxy: {
1243
+ type: "boolean",
1244
+ description: "Serve browser-playable copies of hostile video codecs to the snapshot page (default: off; frames come from FFmpeg, and each copy transcodes the whole clip)",
1245
+ default: void 0
1246
+ },
1247
+ "browser-gpu": {
1248
+ type: "boolean",
1249
+ description: "Use hardware browser GPU capture; pass --no-browser-gpu for deterministic SwiftShader (default: auto-detect, PRODUCER_BROWSER_GPU_MODE overrides)",
1250
+ default: void 0
1251
+ }
1252
+ },
1253
+ async run({ args }) {
1254
+ const asJson = args.json === true;
1255
+ const log = (message) => asJson ? console.error(message) : console.log(message);
1256
+ const fail = (message) => {
1257
+ if (asJson) console.log(JSON.stringify(withMeta({ ok: false, error: message }), null, 2));
1258
+ else log(message);
1259
+ failCommand();
1260
+ };
1261
+ if ([args.at, args.range, args.composition].filter((value) => value !== void 0).length > 1)
1262
+ throw new Error("Use only one of --at, --range, or --composition");
1263
+ const reviewMode = args.review === true;
1264
+ if (reviewMode) {
1265
+ const clash = ["at", "range", "zoom", "keep", "against"].find(
1266
+ (flag) => args[flag] !== void 0 && args[flag] !== false
1267
+ );
1268
+ if (clash) throw new Error(`--review picks its own frames; drop --${clash}`);
1269
+ }
1270
+ const frames = parseInt(args.frames, 10) || 5;
1271
+ const range = args.range === void 0 ? void 0 : parseSnapshotRange(String(args.range));
1272
+ const atTimestamps = args.at === void 0 ? void 0 : parseSnapshotAt(String(args.at));
1273
+ if (args.keep === true && atTimestamps) assertDistinctKeepNames(atTimestamps);
1274
+ const composition = args.composition === void 0 ? void 0 : String(args.composition).trim();
1275
+ if (composition === "") throw new Error("--composition requires a composition ID");
1276
+ const project = resolveProject(args.dir);
1277
+ const lintResult = await lintProject(project.dir);
1278
+ if (hasDefinitiveEntryMismatch(lintResult)) {
1279
+ const candidate = definitiveEntryMismatchComposition(lintResult);
1280
+ log("");
1281
+ for (const line of formatLintFindings(lintResult, { errorsFirst: true })) {
1282
+ log(line);
1283
+ }
1284
+ log("");
1285
+ log(c.error(" Aborting snapshot because the default index.html entry is blank."));
1286
+ if (candidate && posix.basename(candidate) === "index.html") {
1287
+ const candidateDir = posix.dirname(candidate);
1288
+ const target = `<project>/${candidateDir}`;
1289
+ log(
1290
+ c.dim(
1291
+ ` Move or mount the authored file, or snapshot its directory directly: framey snapshot ${target}. Only use the directory form when its assets are self-contained under that directory; otherwise mount it from the project root.`
1292
+ )
1293
+ );
1294
+ } else if (candidate) {
1295
+ log(
1296
+ c.dim(
1297
+ ` Move or mount ${candidate} as a project index.html before snapshotting; snapshot accepts project directories, not individual HTML files.`
1298
+ )
1299
+ );
1300
+ }
1301
+ log("");
1302
+ if (asJson) {
1303
+ const error = "The default index.html entry is blank; see the lint findings on stderr.";
1304
+ console.log(JSON.stringify(withMeta({ ok: false, error }), null, 2));
1305
+ }
1306
+ failCommand();
1307
+ }
1308
+ const timeout = parseInt(args.timeout, 10) || 5e3;
1309
+ const describeArg = args.describe === void 0 ? "true" : String(args.describe) === "false" ? null : String(args.describe);
1310
+ const camera = args.angle ? parseAngle(String(args.angle)) : void 0;
1311
+ const zoomTarget = args.zoom ? parseZoomTarget(String(args.zoom)) : void 0;
1312
+ const zoomScale = parseZoomScale(args["zoom-scale"]);
1313
+ const against = args.against ? resolve(String(args.against)) : void 0;
1314
+ if (against && !existsSync(against))
1315
+ fail(`${c.error("\u2717")} --against video not found: ${against}`);
1316
+ const label = reviewMode ? "review frames (beats, seams, phone)" : atTimestamps ? `${atTimestamps.length} frames at [${atTimestamps.map(formatSnapshotTimestamp).join(", ")}]` : `${frames} frames`;
1317
+ const angleLabel = camera && (camera.yaw !== 0 || camera.pitch !== 0) ? ` ${c.dim(`(angle yaw ${camera.yaw}\xB0 pitch ${camera.pitch}\xB0)`)}` : "";
1318
+ log(`${c.accent("\u25C6")} Capturing ${label} from ${c.accent(project.name)}${angleLabel}`);
1319
+ let managedSnapshotDir;
1320
+ try {
1321
+ const snapshotDir = args.output ? resolve(String(args.output)) : artifactDirectory(project.dir, "snapshots");
1322
+ if (!args.output) managedSnapshotDir = snapshotDir;
1323
+ if (args.output) retainSnapshotOutput(project.dir, snapshotDir);
1324
+ await withProjectLock(project.dir, "snapshot", async () => {
1325
+ const captured = await captureSnapshots(project.dir, {
1326
+ frames,
1327
+ range,
1328
+ composition,
1329
+ timeout,
1330
+ at: atTimestamps,
1331
+ outputDir: snapshotDir,
1332
+ angle: camera,
1333
+ includeEnd: args.end !== false,
1334
+ zoom: zoomTarget,
1335
+ zoomScale,
1336
+ autoProxy: args.proxy,
1337
+ browserGpuMode: resolveLocalBrowserGpuMode(args["browser-gpu"]),
1338
+ against,
1339
+ keep: args.keep === true,
1340
+ review: reviewMode,
1341
+ log
1342
+ });
1343
+ const paths = captured.paths;
1344
+ const framePaths = paths.map((path) => resolve(project.dir, path));
1345
+ reportArtifacts("snapshot", "frame", framePaths);
1346
+ if (paths.length === 0) {
1347
+ fail(`
1348
+ ${c.error("\u2717")} Could not determine composition duration \u2014 no frames captured`);
1349
+ }
1350
+ log(`
1351
+ ${c.success("\u25C7")} ${paths.length} snapshots saved to ${snapshotDir}`);
1352
+ for (const p of paths) {
1353
+ log(` ${p}`);
1354
+ }
1355
+ if (against) {
1356
+ log(
1357
+ ` ${c.dim("ref-*.png + pair-*.jpg")} beside each frame (reference frame, render | reference sheet)`
1358
+ );
1359
+ }
1360
+ let contactSheets = [];
1361
+ let reviewSheets;
1362
+ try {
1363
+ const { createSnapshotContactSheet } = await import("./contactSheet-7W6GYHBQ.js");
1364
+ if (captured.review) {
1365
+ const written = new Map(
1366
+ captured.review.frames.map((frame) => [frame.file, frame.path])
1367
+ );
1368
+ reviewSheets = await writeReviewSheets(
1369
+ snapshotDir,
1370
+ captured.review.frames,
1371
+ (frame) => written.get(frame.file) ?? null
1372
+ );
1373
+ contactSheets = [...reviewSheets.beats, ...reviewSheets.seams, ...reviewSheets.phone];
1374
+ printReview(captured.review, reviewSheets, log);
1375
+ } else if (args.keep !== true) {
1376
+ contactSheets = await createSnapshotContactSheet(
1377
+ framePaths,
1378
+ join(snapshotDir, "contact-sheet.jpg")
1379
+ );
1380
+ }
1381
+ reportArtifacts("snapshot", "contact-sheet", contactSheets);
1382
+ if (!captured.review) {
1383
+ for (const sheet of contactSheets) {
1384
+ log(` ${resolve(sheet)} (grid view for AI review)`);
1385
+ }
1386
+ }
1387
+ } catch (err) {
1388
+ console.warn(
1389
+ ` ${c.warn("\u26A0")} Contact sheet skipped: ${normalizeErrorMessage(err)}. Individual snapshot PNGs are still available.`
1390
+ );
1391
+ }
1392
+ let describedPath;
1393
+ if (describeArg !== null) {
1394
+ try {
1395
+ const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
1396
+ if (!geminiKey) {
1397
+ if (args.describe !== void 0)
1398
+ log(` ${c.dim("--describe: GEMINI_API_KEY not set, skipping")}`);
1399
+ } else if (paths.length > 0) {
1400
+ log(` ${c.dim("Describing frames with Gemini vision...")}`);
1401
+ const { GoogleGenAI } = await loadOptionalPackage("@google/genai", "--describe");
1402
+ const ai = new GoogleGenAI({ apiKey: geminiKey });
1403
+ const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
1404
+ const customQuestion = describeArg === "true" ? "Describe this video composition frame in 1-2 sentences. Be specific and factual: what elements are visible, what text appears, is the frame blank/black/loading, what is the composition. Flag any obvious problems." : describeArg;
1405
+ const descriptions = [
1406
+ `# Snapshot Frame Descriptions`,
1407
+ ``,
1408
+ `**Question asked:** ${customQuestion}`,
1409
+ ``,
1410
+ `Compare each description against your storyboard spec. A "black frame" or "loading screen" for a content beat is a bug.`,
1411
+ ``
1412
+ ];
1413
+ let sharp = null;
1414
+ try {
1415
+ sharp = loadInstalledSharp("--describe");
1416
+ } catch {
1417
+ }
1418
+ const results = await Promise.allSettled(
1419
+ paths.map(async (p) => {
1420
+ const filename = basename(p);
1421
+ const filePath = join(snapshotDir, filename);
1422
+ if (!existsSync(filePath)) return { filename, desc: "file not found" };
1423
+ const raw = readFileSync(filePath);
1424
+ let imageData;
1425
+ let mimeType = "image/png";
1426
+ if (sharp) {
1427
+ imageData = await sharp(raw).resize(960).jpeg().toBuffer();
1428
+ mimeType = "image/jpeg";
1429
+ } else {
1430
+ if (raw.length > 38e5)
1431
+ return {
1432
+ filename,
1433
+ desc: "file too large for Gemini \u2014 install sharp to enable auto-resize"
1434
+ };
1435
+ imageData = raw;
1436
+ }
1437
+ const base64 = imageData.toString("base64");
1438
+ const response = await ai.models.generateContent({
1439
+ model,
1440
+ contents: [
1441
+ {
1442
+ role: "user",
1443
+ parts: [
1444
+ { inlineData: { mimeType, data: base64 } },
1445
+ { text: customQuestion }
1446
+ ]
1447
+ }
1448
+ ],
1449
+ config: { maxOutputTokens: 250 }
1450
+ });
1451
+ return { filename, desc: response.text?.trim() || "no description" };
1452
+ })
1453
+ );
1454
+ for (const result of results) {
1455
+ if (result.status === "fulfilled") {
1456
+ descriptions.push(`## ${result.value.filename}`, `${result.value.desc}`, ``);
1457
+ } else {
1458
+ const errMsg = normalizeErrorMessage(result.reason);
1459
+ descriptions.push(
1460
+ `## (error)`,
1461
+ `Gemini call failed: ${errMsg.slice(0, 120)}`,
1462
+ ``
1463
+ );
1464
+ }
1465
+ }
1466
+ const descPath = join(snapshotDir, "descriptions.md");
1467
+ writeFileSync(descPath, descriptions.join("\n"));
1468
+ describedPath = descPath;
1469
+ log(` ${c.dim("descriptions.md")} (Gemini frame analysis)`);
1470
+ }
1471
+ } catch (descErr) {
1472
+ const msg = normalizeErrorMessage(descErr);
1473
+ log(` ${c.dim(`--describe failed: ${msg}`)}`);
1474
+ }
1475
+ }
1476
+ if (asJson) {
1477
+ const envelope = snapshotEnvelope(project.dir, snapshotDir, captured, {
1478
+ contactSheets,
1479
+ reviewSheets,
1480
+ describedPath
1481
+ });
1482
+ console.log(JSON.stringify(withMeta(envelope), null, 2));
1483
+ }
1484
+ });
1485
+ } catch (err) {
1486
+ if (err instanceof CliRuntimeError && err.result.presented) throw err;
1487
+ const msg = normalizeErrorMessage(err);
1488
+ if (asJson) console.log(JSON.stringify(withMeta({ ok: false, error: msg }), null, 2));
1489
+ else console.error(`
1490
+ ${c.error("\u2717")} Snapshot failed: ${msg}`);
1491
+ failCommand();
1492
+ } finally {
1493
+ if (managedSnapshotDir)
1494
+ await finishSnapshotRun(project.dir, managedSnapshotDir).catch(warnSnapshotsNotRecorded);
1495
+ }
1496
+ }
1497
+ });
1498
+ export {
1499
+ SNAPSHOT_IMAGE_NAME,
1500
+ computeSnapshotTimes,
1501
+ containingSourceFrameIndex,
1502
+ snapshot_default as default,
1503
+ examples,
1504
+ extractVideoFrameToBuffer,
1505
+ formatSnapshotTimestamp,
1506
+ parseZoomScale,
1507
+ probeContainingSourceFrameSeek,
1508
+ recaptureSnapshotComposite,
1509
+ requireSnapshotFfmpeg,
1510
+ resolveSnapshotVideoClipStart,
1511
+ resolveSnapshotVideoFrameTime,
1512
+ resolveSnapshotVideoRateSpec,
1513
+ serveCapturePage,
1514
+ tailFrameTime
1515
+ };