@hraness/slopcamera 3.2.4

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 (441) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +60 -0
  3. package/PRIVACY.md +46 -0
  4. package/README.md +311 -0
  5. package/SECURITY.md +21 -0
  6. package/apps/desktop/README.md +966 -0
  7. package/apps/desktop/analysis/protocol.ts +223 -0
  8. package/apps/desktop/application/capability-binding.ts +594 -0
  9. package/apps/desktop/application/context.ts +97 -0
  10. package/apps/desktop/application/creative-iteration.ts +1385 -0
  11. package/apps/desktop/application/default-registry.ts +146 -0
  12. package/apps/desktop/application/directing-contract.ts +218 -0
  13. package/apps/desktop/application/directing-plan.ts +178 -0
  14. package/apps/desktop/application/errors.ts +18 -0
  15. package/apps/desktop/application/gateway-port.ts +867 -0
  16. package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
  17. package/apps/desktop/application/html-overlay-integrity.ts +337 -0
  18. package/apps/desktop/application/html-overlay-renderer.ts +45 -0
  19. package/apps/desktop/application/index.ts +13 -0
  20. package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
  21. package/apps/desktop/application/operation-effects.ts +94 -0
  22. package/apps/desktop/application/operation.ts +224 -0
  23. package/apps/desktop/application/operations/analysis/faces.ts +244 -0
  24. package/apps/desktop/application/operations/analysis/music.ts +248 -0
  25. package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
  26. package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
  27. package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
  28. package/apps/desktop/application/operations/analysis/shared.ts +202 -0
  29. package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
  30. package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
  31. package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
  32. package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
  33. package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
  34. package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
  35. package/apps/desktop/application/operations/gateway/image.ts +41 -0
  36. package/apps/desktop/application/operations/gateway/index.ts +17 -0
  37. package/apps/desktop/application/operations/gateway/shared.ts +122 -0
  38. package/apps/desktop/application/operations/gateway/speech.ts +41 -0
  39. package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
  40. package/apps/desktop/application/operations/gateway/video.ts +41 -0
  41. package/apps/desktop/application/operations/index.ts +31 -0
  42. package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
  43. package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
  44. package/apps/desktop/application/operations/iteration/select.ts +155 -0
  45. package/apps/desktop/application/operations/iteration/shared.ts +250 -0
  46. package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
  47. package/apps/desktop/application/operations/media/binding.ts +79 -0
  48. package/apps/desktop/application/operations/media/capabilities.ts +112 -0
  49. package/apps/desktop/application/operations/media/color-grade.ts +343 -0
  50. package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
  51. package/apps/desktop/application/operations/media/index.ts +8 -0
  52. package/apps/desktop/application/operations/media/ingest.ts +303 -0
  53. package/apps/desktop/application/operations/media/overlay.ts +729 -0
  54. package/apps/desktop/application/operations/media/shared.ts +845 -0
  55. package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
  56. package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
  57. package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
  58. package/apps/desktop/application/operations/project/snapshot.ts +95 -0
  59. package/apps/desktop/application/operations/recording/index.ts +5 -0
  60. package/apps/desktop/application/operations/recording/pause.ts +34 -0
  61. package/apps/desktop/application/operations/recording/resume.ts +34 -0
  62. package/apps/desktop/application/operations/recording/shared.ts +199 -0
  63. package/apps/desktop/application/operations/recording/start.ts +169 -0
  64. package/apps/desktop/application/operations/recording/stop.ts +34 -0
  65. package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
  66. package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
  67. package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
  68. package/apps/desktop/application/operations/render/project-plan.ts +939 -0
  69. package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
  70. package/apps/desktop/application/operations/render/project.ts +1922 -0
  71. package/apps/desktop/application/operations/shared.ts +67 -0
  72. package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
  73. package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
  74. package/apps/desktop/application/operations/spatial-project.ts +209 -0
  75. package/apps/desktop/application/operations/spatial-render.ts +415 -0
  76. package/apps/desktop/application/operations/spatial-scene.ts +97 -0
  77. package/apps/desktop/application/operations/studio.ts +143 -0
  78. package/apps/desktop/application/output-publication-lease.ts +163 -0
  79. package/apps/desktop/application/project-publication-lease.ts +233 -0
  80. package/apps/desktop/application/project-store.ts +196 -0
  81. package/apps/desktop/application/receipts.ts +533 -0
  82. package/apps/desktop/application/registry.ts +182 -0
  83. package/apps/desktop/application/spatial-assets.ts +675 -0
  84. package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
  85. package/apps/desktop/application/spatial-project-authority.ts +35 -0
  86. package/apps/desktop/application/spatial-project-lease.ts +126 -0
  87. package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
  88. package/apps/desktop/application/spatial-project-store.ts +286 -0
  89. package/apps/desktop/application/spatial-render-projection.ts +201 -0
  90. package/apps/desktop/application/spatial-render.ts +677 -0
  91. package/apps/desktop/application/spatial-spz.ts +44 -0
  92. package/apps/desktop/application/spatial-world-import.ts +135 -0
  93. package/apps/desktop/application/spatial-world-provenance.ts +46 -0
  94. package/apps/desktop/application/studio-port.ts +58 -0
  95. package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
  96. package/apps/desktop/capture/protocol.ts +689 -0
  97. package/apps/desktop/cli/analyzer.ts +406 -0
  98. package/apps/desktop/cli/args.ts +3181 -0
  99. package/apps/desktop/cli/asset-ingest.ts +747 -0
  100. package/apps/desktop/cli/atomic-render-effects.ts +210 -0
  101. package/apps/desktop/cli/atomic-render-platform.ts +205 -0
  102. package/apps/desktop/cli/atomic-render.ts +50 -0
  103. package/apps/desktop/cli/audio-analysis.ts +252 -0
  104. package/apps/desktop/cli/bounded-map.ts +39 -0
  105. package/apps/desktop/cli/bundle-service.ts +256 -0
  106. package/apps/desktop/cli/capabilities.ts +212 -0
  107. package/apps/desktop/cli/capture-bundle.ts +1117 -0
  108. package/apps/desktop/cli/command-host-resources.ts +251 -0
  109. package/apps/desktop/cli/commands.ts +6836 -0
  110. package/apps/desktop/cli/directing-blob.ts +291 -0
  111. package/apps/desktop/cli/directing-media.ts +517 -0
  112. package/apps/desktop/cli/directing-quote.ts +89 -0
  113. package/apps/desktop/cli/directing-service.ts +288 -0
  114. package/apps/desktop/cli/emoji-assets.ts +538 -0
  115. package/apps/desktop/cli/errors.ts +29 -0
  116. package/apps/desktop/cli/face-analysis-service.ts +940 -0
  117. package/apps/desktop/cli/filter-script.ts +76 -0
  118. package/apps/desktop/cli/gateway-application-port.ts +1142 -0
  119. package/apps/desktop/cli/gateway-credential.ts +81 -0
  120. package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
  121. package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
  122. package/apps/desktop/cli/gateway-media-service.ts +3106 -0
  123. package/apps/desktop/cli/gateway-media-signature.ts +122 -0
  124. package/apps/desktop/cli/gateway-provider-options.ts +562 -0
  125. package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
  126. package/apps/desktop/cli/help.ts +501 -0
  127. package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
  128. package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
  129. package/apps/desktop/cli/io.ts +290 -0
  130. package/apps/desktop/cli/main.ts +123 -0
  131. package/apps/desktop/cli/media-effects-service.ts +885 -0
  132. package/apps/desktop/cli/media-ingest-model.ts +364 -0
  133. package/apps/desktop/cli/media-ingest-platform.ts +227 -0
  134. package/apps/desktop/cli/media-ingest-program.ts +200 -0
  135. package/apps/desktop/cli/media-ingest.ts +30 -0
  136. package/apps/desktop/cli/music-analysis-service.ts +434 -0
  137. package/apps/desktop/cli/mutation-lock.ts +530 -0
  138. package/apps/desktop/cli/native-assets.d.ts +12 -0
  139. package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
  140. package/apps/desktop/cli/overlay-playback.ts +177 -0
  141. package/apps/desktop/cli/paths.ts +265 -0
  142. package/apps/desktop/cli/portable-surface.ts +242 -0
  143. package/apps/desktop/cli/project-camera-receipt.ts +112 -0
  144. package/apps/desktop/cli/project-face-camera.ts +394 -0
  145. package/apps/desktop/cli/project-inactivity-service.ts +552 -0
  146. package/apps/desktop/cli/project-media-integrity.ts +121 -0
  147. package/apps/desktop/cli/project-renderer.ts +1526 -0
  148. package/apps/desktop/cli/project-service.ts +519 -0
  149. package/apps/desktop/cli/project-state-transaction.ts +520 -0
  150. package/apps/desktop/cli/query-limits.ts +1 -0
  151. package/apps/desktop/cli/recording-controller.ts +1567 -0
  152. package/apps/desktop/cli/recording-daemon.ts +931 -0
  153. package/apps/desktop/cli/recording-ref.ts +56 -0
  154. package/apps/desktop/cli/renamed-environment.ts +6 -0
  155. package/apps/desktop/cli/renderer.ts +1439 -0
  156. package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
  157. package/apps/desktop/cli/scene-analysis-service.ts +879 -0
  158. package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
  159. package/apps/desktop/cli/spatial-project-service.ts +90 -0
  160. package/apps/desktop/cli/spatial-scene-service.ts +105 -0
  161. package/apps/desktop/cli/spatial-world-service.ts +39 -0
  162. package/apps/desktop/cli/speech-analysis-service.ts +682 -0
  163. package/apps/desktop/cli/studio-assemble.ts +58 -0
  164. package/apps/desktop/cli/studio-command.ts +60 -0
  165. package/apps/desktop/cli/studio-custody.ts +60 -0
  166. package/apps/desktop/cli/studio-encode.ts +216 -0
  167. package/apps/desktop/cli/studio-exr.ts +52 -0
  168. package/apps/desktop/cli/studio-files.ts +179 -0
  169. package/apps/desktop/cli/studio-output-validation.ts +162 -0
  170. package/apps/desktop/cli/studio-process.ts +145 -0
  171. package/apps/desktop/cli/studio-runtime.ts +77 -0
  172. package/apps/desktop/cli/studio-scaffold.ts +70 -0
  173. package/apps/desktop/cli/studio-service.ts +301 -0
  174. package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
  175. package/apps/desktop/cli/studio-template-names.ts +3 -0
  176. package/apps/desktop/cli/studio-workflow.ts +19 -0
  177. package/apps/desktop/cli/time.ts +25 -0
  178. package/apps/desktop/cli/workflow-code.ts +584 -0
  179. package/apps/desktop/cli/workflow-runs.ts +576 -0
  180. package/apps/desktop/code/advanced.ts +139 -0
  181. package/apps/desktop/code/application-node-planner.ts +1288 -0
  182. package/apps/desktop/code/compiler.ts +329 -0
  183. package/apps/desktop/code/contracts.ts +13 -0
  184. package/apps/desktop/code/creative-recipes.ts +94 -0
  185. package/apps/desktop/code/define-workflow.ts +115 -0
  186. package/apps/desktop/code/editing.ts +284 -0
  187. package/apps/desktop/code/file-candidate-provenance.ts +420 -0
  188. package/apps/desktop/code/file-candidate.ts +69 -0
  189. package/apps/desktop/code/graph-builder.ts +10 -0
  190. package/apps/desktop/code/host-resource-policy.ts +55 -0
  191. package/apps/desktop/code/plan-contracts.ts +159 -0
  192. package/apps/desktop/code/planning.ts +361 -0
  193. package/apps/desktop/code/public.ts +134 -0
  194. package/apps/desktop/code/reference-projection.ts +62 -0
  195. package/apps/desktop/code/run-contracts.ts +466 -0
  196. package/apps/desktop/code/run-store.ts +2478 -0
  197. package/apps/desktop/code/runtime-identity.ts +328 -0
  198. package/apps/desktop/code/scheduler.ts +3067 -0
  199. package/apps/desktop/code/semantic-builder.ts +1436 -0
  200. package/apps/desktop/code/source-bundle.ts +1641 -0
  201. package/apps/desktop/code/source-typecheck.ts +211 -0
  202. package/apps/desktop/code/testing.ts +79 -0
  203. package/apps/desktop/code/worker-client.ts +1675 -0
  204. package/apps/desktop/code/worker-effects.ts +141 -0
  205. package/apps/desktop/code/worker-entry.ts +435 -0
  206. package/apps/desktop/code/worker-lease-guardian.ts +179 -0
  207. package/apps/desktop/code/worker-process-identity.ts +484 -0
  208. package/apps/desktop/code/worker-protocol.ts +211 -0
  209. package/apps/desktop/code/workflow-effects.ts +132 -0
  210. package/apps/desktop/contracts/analysis.ts +949 -0
  211. package/apps/desktop/contracts/edit.ts +576 -0
  212. package/apps/desktop/contracts/index.ts +9 -0
  213. package/apps/desktop/contracts/media-effects.ts +139 -0
  214. package/apps/desktop/contracts/project-render.ts +644 -0
  215. package/apps/desktop/contracts/project.ts +740 -0
  216. package/apps/desktop/contracts/recording.ts +1434 -0
  217. package/apps/desktop/contracts/render.ts +334 -0
  218. package/apps/desktop/contracts/runtime.ts +155 -0
  219. package/apps/desktop/contracts/spatial-compositor.ts +58 -0
  220. package/apps/desktop/contracts/spatial-project.ts +159 -0
  221. package/apps/desktop/contracts/spatial-world.ts +48 -0
  222. package/apps/desktop/contracts/video-effects.ts +120 -0
  223. package/apps/desktop/core/alignment-apply.ts +400 -0
  224. package/apps/desktop/core/audio-alignment.ts +466 -0
  225. package/apps/desktop/core/auto-zoom.ts +209 -0
  226. package/apps/desktop/core/canonical-json.ts +14 -0
  227. package/apps/desktop/core/emoji.ts +210 -0
  228. package/apps/desktop/core/events.ts +409 -0
  229. package/apps/desktop/core/face-framing.ts +533 -0
  230. package/apps/desktop/core/face-tracks.ts +326 -0
  231. package/apps/desktop/core/inactivity.ts +137 -0
  232. package/apps/desktop/core/index.ts +30 -0
  233. package/apps/desktop/core/intervals.ts +123 -0
  234. package/apps/desktop/core/montage.ts +236 -0
  235. package/apps/desktop/core/music-analysis.ts +518 -0
  236. package/apps/desktop/core/plan.ts +244 -0
  237. package/apps/desktop/core/project-auto-zoom.ts +75 -0
  238. package/apps/desktop/core/project-camera-spatial.ts +676 -0
  239. package/apps/desktop/core/project-camera.ts +403 -0
  240. package/apps/desktop/core/project-captions.ts +888 -0
  241. package/apps/desktop/core/project-inactivity.ts +125 -0
  242. package/apps/desktop/core/project-metadata.ts +927 -0
  243. package/apps/desktop/core/project-plan.ts +375 -0
  244. package/apps/desktop/core/project-render-plan.ts +303 -0
  245. package/apps/desktop/core/project-time.ts +469 -0
  246. package/apps/desktop/core/render-plan.ts +442 -0
  247. package/apps/desktop/core/scene-cache.ts +174 -0
  248. package/apps/desktop/core/scene-sampling.ts +448 -0
  249. package/apps/desktop/core/spatial-compositor.ts +22 -0
  250. package/apps/desktop/core/spatial-durability.ts +99 -0
  251. package/apps/desktop/core/spatial-project.ts +169 -0
  252. package/apps/desktop/core/spatial-shot-clock.ts +32 -0
  253. package/apps/desktop/core/speech-analysis.ts +333 -0
  254. package/apps/desktop/core/storage.ts +970 -0
  255. package/apps/desktop/core/time-map.ts +91 -0
  256. package/apps/desktop/core/time.ts +61 -0
  257. package/apps/desktop/core/video-effects.ts +660 -0
  258. package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
  259. package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  260. package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  261. package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
  262. package/apps/desktop/dist/cli/main.js +5086 -0
  263. package/apps/desktop/html-overlay/catalog.ts +171 -0
  264. package/apps/desktop/html-overlay/contracts.ts +372 -0
  265. package/apps/desktop/html-overlay/execution-profile.ts +77 -0
  266. package/apps/desktop/html-overlay/index.ts +98 -0
  267. package/apps/desktop/html-overlay/libraries.ts +263 -0
  268. package/apps/desktop/html-overlay/random.ts +49 -0
  269. package/apps/desktop/html-overlay/runtime.ts +813 -0
  270. package/apps/desktop/html-overlay/scaffolds.ts +792 -0
  271. package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
  272. package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
  273. package/apps/desktop/html-overlay/spatial.ts +720 -0
  274. package/apps/desktop/studio/assets/README.md +39 -0
  275. package/apps/desktop/studio/assets/contracts.ts +129 -0
  276. package/apps/desktop/studio/assets/network.ts +64 -0
  277. package/apps/desktop/studio/assets/poly-haven.ts +148 -0
  278. package/apps/desktop/studio/drivers/blender_driver.py +466 -0
  279. package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
  280. package/apps/desktop/studio/education/driver.py +308 -0
  281. package/apps/desktop/studio/education/lesson.py +180 -0
  282. package/apps/desktop/studio/education/toolkit.py +249 -0
  283. package/apps/desktop/workflows/chaptered-demo.ts +48 -0
  284. package/apps/desktop/workflows/creative-iteration.ts +107 -0
  285. package/apps/desktop/workflows/creative-selection.ts +92 -0
  286. package/apps/desktop/workflows/fragments.ts +486 -0
  287. package/apps/desktop/workflows/index.ts +93 -0
  288. package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
  289. package/apps/desktop/workflows/scene-direction.ts +12 -0
  290. package/apps/desktop/workflows/social-variants.ts +75 -0
  291. package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
  292. package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
  293. package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  294. package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  295. package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
  296. package/dist/cli.js +479 -0
  297. package/dist/code/advanced.js +270 -0
  298. package/dist/code/index.js +2240 -0
  299. package/dist/generate.js +24 -0
  300. package/dist/host-resources.js +26 -0
  301. package/dist/index-42zsesc1.js +2257 -0
  302. package/dist/index-7308egqr.js +48 -0
  303. package/dist/index-8txs6fkn.js +712 -0
  304. package/dist/index-mcy8z0br.js +192 -0
  305. package/dist/index-p63wavx0.js +2029 -0
  306. package/dist/index-r7gdhmsp.js +424 -0
  307. package/dist/index-sh6xbav6.js +1219 -0
  308. package/dist/index-z1w83f81.js +4 -0
  309. package/dist/index-z7239b4h.js +2363 -0
  310. package/dist/index-zfeg9hmw.js +2013 -0
  311. package/dist/index.js +241 -0
  312. package/dist/operations.js +29 -0
  313. package/dist/skill-install-dh0ntqba.js +10 -0
  314. package/dist/vectorize/worker.js +142 -0
  315. package/dist/workflow.js +16 -0
  316. package/docs/README.md +38 -0
  317. package/docs/studio.md +215 -0
  318. package/examples/studio/blender/README.md +26 -0
  319. package/examples/studio/blender/character.py +152 -0
  320. package/examples/studio/blender/cloth.py +101 -0
  321. package/examples/studio/blender/color_chart.py +29 -0
  322. package/examples/studio/blender/fluid.py +78 -0
  323. package/examples/studio/blender/import_model.py +74 -0
  324. package/examples/studio/blender/product.py +53 -0
  325. package/examples/studio/blender/shaded_street.py +481 -0
  326. package/examples/studio/blender/studio_scene.py +147 -0
  327. package/examples/studio/cadquery/README.md +13 -0
  328. package/examples/studio/cadquery/bracket.py +36 -0
  329. package/examples/studio/cadquery/import_step.py +12 -0
  330. package/examples/studio/education/README.md +46 -0
  331. package/examples/studio/education/lesson.json +99 -0
  332. package/examples/studio/education/scene.py +7 -0
  333. package/examples/studio/hybrid-scene.ts +96 -0
  334. package/examples/studio/native-workflow.ts +14 -0
  335. package/examples/studio/vgpu/README.md +64 -0
  336. package/examples/studio/vgpu/heat-field.mjs +290 -0
  337. package/package.json +304 -0
  338. package/packages/scene/src/index.ts +1 -0
  339. package/packages/scene/src/scene.ts +215 -0
  340. package/schema/diagram.schema.json +334 -0
  341. package/skills/slopcamera/SKILL.md +45 -0
  342. package/skills/slopcamera/agents/openai.yaml +4 -0
  343. package/skills/slopcamera/references/customization.md +77 -0
  344. package/skills/slopcamera/references/diagrams.md +153 -0
  345. package/skills/slopcamera/references/directed-scenes.md +42 -0
  346. package/skills/slopcamera/references/directing-video.md +18 -0
  347. package/skills/slopcamera/references/educational-video.md +26 -0
  348. package/skills/slopcamera/references/gateway-media.md +134 -0
  349. package/skills/slopcamera/references/install.md +62 -0
  350. package/skills/slopcamera/references/native-studio.md +31 -0
  351. package/skills/slopcamera/references/reference-led-3d.md +140 -0
  352. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
  353. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
  354. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
  355. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
  356. package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
  357. package/skills/slopcamera/references/vectorization.md +29 -0
  358. package/skills/slopcamera/references/video-projects.md +187 -0
  359. package/skills/slopcamera/references/visual-communication.md +120 -0
  360. package/skills/slopcamera/references/workflows-sdk.md +45 -0
  361. package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
  362. package/src/artifacts.ts +106 -0
  363. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  364. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  365. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  366. package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  367. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  368. package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
  369. package/src/assets.d.ts +9 -0
  370. package/src/cli.ts +601 -0
  371. package/src/cloud-errors.ts +26 -0
  372. package/src/code/advanced.ts +10 -0
  373. package/src/code/boundary.ts +21 -0
  374. package/src/code/canonical-json.ts +129 -0
  375. package/src/code/compiler.ts +953 -0
  376. package/src/code/contracts.ts +576 -0
  377. package/src/code/define-workflow.ts +334 -0
  378. package/src/code/errors.ts +41 -0
  379. package/src/code/graph-builder.ts +945 -0
  380. package/src/code/index.ts +121 -0
  381. package/src/code/json-snapshot.ts +899 -0
  382. package/src/code/json-utf8.ts +88 -0
  383. package/src/code/portable-builder.ts +136 -0
  384. package/src/code/projection.ts +378 -0
  385. package/src/code/public-operations.ts +450 -0
  386. package/src/code/runtime.ts +580 -0
  387. package/src/code/sha256.ts +36 -0
  388. package/src/config.ts +180 -0
  389. package/src/desktop.ts +253 -0
  390. package/src/fs.ts +10 -0
  391. package/src/generate.ts +704 -0
  392. package/src/host-resource-posix.ts +161 -0
  393. package/src/host-resources.ts +1749 -0
  394. package/src/icons.ts +58 -0
  395. package/src/index.ts +200 -0
  396. package/src/label-layout.ts +130 -0
  397. package/src/layout.ts +413 -0
  398. package/src/lint.ts +127 -0
  399. package/src/mcp/boundary.ts +354 -0
  400. package/src/mcp/index.ts +33 -0
  401. package/src/mcp/server.ts +272 -0
  402. package/src/mcp/tools.ts +963 -0
  403. package/src/mcp/types.ts +71 -0
  404. package/src/operations.ts +775 -0
  405. package/src/parse.ts +861 -0
  406. package/src/process-environment.ts +17 -0
  407. package/src/render.ts +437 -0
  408. package/src/skill-install.ts +49 -0
  409. package/src/spatial-scene/authoring.ts +27 -0
  410. package/src/spatial-scene/camera-track.ts +83 -0
  411. package/src/spatial-scene/contracts.ts +277 -0
  412. package/src/spatial-scene/evaluate.ts +145 -0
  413. package/src/spatial-scene/gltf.ts +558 -0
  414. package/src/spatial-scene/identity.ts +260 -0
  415. package/src/spatial-scene/index.ts +10 -0
  416. package/src/spatial-scene/inspect.ts +68 -0
  417. package/src/spatial-scene/math.ts +280 -0
  418. package/src/spatial-scene/patch.ts +131 -0
  419. package/src/spatial-scene/time.ts +53 -0
  420. package/src/studio/contracts.ts +129 -0
  421. package/src/studio/index.ts +2 -0
  422. package/src/studio/plan.ts +156 -0
  423. package/src/studio/shared.ts +41 -0
  424. package/src/theme.ts +68 -0
  425. package/src/tldr.ts +474 -0
  426. package/src/types.ts +212 -0
  427. package/src/vectorize/archive.ts +101 -0
  428. package/src/vectorize/command.ts +797 -0
  429. package/src/vectorize/index.ts +27 -0
  430. package/src/vectorize/limits.ts +61 -0
  431. package/src/vectorize/metrics.ts +351 -0
  432. package/src/vectorize/pixels.ts +323 -0
  433. package/src/vectorize/supervisor.ts +339 -0
  434. package/src/vectorize/svg.ts +188 -0
  435. package/src/vectorize/tool.ts +542 -0
  436. package/src/vectorize/types.ts +113 -0
  437. package/src/vectorize/vectorize.ts +634 -0
  438. package/src/vectorize/worker-protocol.ts +40 -0
  439. package/src/vectorize/worker.ts +209 -0
  440. package/src/version.ts +1 -0
  441. package/src/workflow.ts +409 -0
@@ -0,0 +1,58 @@
1
+ import { join } from "node:path";
2
+ import { z } from "zod";
3
+ import { boundedCanonicalJsonSha256 } from "../../../src/code/canonical-json";
4
+ import { parseStudioPlan, validateStudioReceipt } from "../../../src/studio";
5
+ import { GatewayMediaSourceReferenceSchema, type GatewayMediaSourceReference } from "../application/gateway-port";
6
+ import { createNodeBundleFileSystem } from "../core/storage";
7
+ import { assembleDirectingClips, inspectDirectingClipDuration, type DirectingAssemblyResult } from "./directing-media";
8
+ import { CliError } from "./errors";
9
+ import { encodeStudioSequence, type StudioEncodeInput } from "./studio-encode";
10
+ import { studioJson } from "./studio-files";
11
+ import { studioStorageRoot } from "./studio-service";
12
+
13
+ export interface StudioAssemblyInput extends StudioEncodeInput { readonly title?: string; }
14
+ export interface StudioAssemblyResult extends DirectingAssemblyResult {
15
+ readonly kind: "slopcamera.studio-assembly"; readonly schemaVersion: 1;
16
+ readonly jobId: string; readonly outputId: string;
17
+ readonly clip: GatewayMediaSourceReference;
18
+ readonly encoded: Awaited<ReturnType<typeof encodeStudioSequence>>;
19
+ }
20
+
21
+ /** Native source stays authoritative; the delivery derivative enters the existing V1 project. */
22
+ export async function assembleStudioJob(input: StudioAssemblyInput): Promise<StudioAssemblyResult> {
23
+ if (!/^studio_[a-zA-Z0-9][a-zA-Z0-9_-]{0,120}$/u.test(input.jobId)) throw new CliError("invalid-data", "Invalid studio job ID.");
24
+ const title = z.string().trim().min(1).max(512).parse(input.title ?? `Studio ${input.jobId}`);
25
+ const fence = async () => {
26
+ if (input.signal.aborted) throw new CliError("cancelled", "Studio assembly was cancelled.");
27
+ await input.application.hostResourceLease?.assertOwned();
28
+ await input.beforePublication();
29
+ if (input.signal.aborted) throw new CliError("cancelled", "Studio assembly was cancelled.");
30
+ };
31
+ await fence();
32
+ const native = await input.service.inspect(input.jobId);
33
+ const fs = createNodeBundleFileSystem(join(await studioStorageRoot(input.application), "jobs", input.jobId));
34
+ const plan = parseStudioPlan(JSON.parse(await fs.readText("plan.json", 32 * 1024 * 1024)) as unknown);
35
+ const receipt = validateStudioReceipt({ plan, receipt: native.document });
36
+ if (receipt.state !== "succeeded" || receipt.jobId !== input.jobId) throw new CliError("invalid-data", "Studio assembly requires a successful retained job.");
37
+ const output = plan.job.outputs.find(item => item.id === input.outputId), render = plan.job.render;
38
+ if (output?.kind !== "sequence" || output.format !== "png" || output.interpretation.kind !== "raster" || output.interpretation.semantic !== "color"
39
+ || output.interpretation.colorSpace !== "srgb" || output.interpretation.alpha !== "opaque" || render === undefined) throw new CliError("unsupported-plan", "Studio assemble currently accepts opaque sRGB PNG sequences. Encode straight-alpha sequences separately for overlay composition.");
40
+ const count = render.endFrameExclusive - render.startFrame;
41
+ const durationNumerator = BigInt(count) * BigInt(render.frameRate.denominator) * 1_000_000n;
42
+ const durationUs = Number((durationNumerator + BigInt(render.frameRate.numerator) / 2n) / BigInt(render.frameRate.numerator));
43
+ // This is the existing media assembler's bounded profile, checked before encoding.
44
+ if (render.width > 4096 || render.height > 4096 || count > 4000 || durationUs > 60_000_000 || durationUs < 1) throw new CliError("unsupported-plan", "Studio assembly currently supports at most 4096 pixels per side, 4000 frames, and 60 seconds per clip.");
45
+ const encoded = await encodeStudioSequence(input);
46
+ if (encoded.document.request.planSha256 !== plan.planSha256 || studioJson(encoded.document.request.nativeReceipt) !== studioJson(native.receipt)) throw new CliError("conflict", "Studio source changed between assembly planning and encoding.");
47
+ const lease = input.application.hostResourceLease!;
48
+ const application = { ...input.application, hostResourceLease: { ...lease, assertOwned: fence } };
49
+ const source = GatewayMediaSourceReferenceSchema.parse({ ...encoded.artifact, mediaType: "video/mp4" });
50
+ const measuredDurationUs = await inspectDirectingClipDuration(application, source, input.signal);
51
+ const clip = GatewayMediaSourceReferenceSchema.parse({ ...source, facts: { width: render.width, height: render.height, durationSeconds: measuredDurationUs / 1_000_000 } });
52
+ const recipeSha256 = boundedCanonicalJsonSha256({ kind: "slopcamera.studio-assembly-selection", schemaVersion: 1, title, jobId: input.jobId, outputId: input.outputId, planSha256: plan.planSha256, nativeReceipt: native.receipt, encodeReceipt: encoded.receipt, clip }, { maximumBytes: 1_048_576 });
53
+ await fence();
54
+ const assembled = await assembleDirectingClips(application, { id: `studio_${recipeSha256.slice(0, 40)}`, title, recipeSha256,
55
+ clips: [{ shotId: input.outputId, attemptId: input.jobId, source: clip, durationUs: measuredDurationUs }] }, input.signal);
56
+ await fence();
57
+ return { kind: "slopcamera.studio-assembly", schemaVersion: 1, jobId: input.jobId, outputId: input.outputId, clip, encoded, ...assembled };
58
+ }
@@ -0,0 +1,60 @@
1
+ import { dirname, relative, resolve } from "node:path";
2
+ import { z } from "zod";
3
+ import { StudioEngineSchema, StudioPathSchema } from "../../../src/studio";
4
+ import type { ApplicationContext } from "../application/context";
5
+ import { createPolyHavenAssetService } from "../studio/assets/poly-haven";
6
+ import type { StudioCommand } from "./args";
7
+ import { CliError } from "./errors";
8
+ import { readSpatialJson } from "./spatial-scene-service";
9
+ import { captureStudioSource, retainStudioSource } from "./studio-files";
10
+ import { assembleStudioJob } from "./studio-assemble";
11
+ import { encodeStudioSequence } from "./studio-encode";
12
+ import { admitStudioSpatialAsset } from "./studio-spatial-asset";
13
+ import { createStudioScaffold } from "./studio-scaffold";
14
+ import { createStudioService, studioStorageRoot } from "./studio-service";
15
+ import { ensurePhysicalPrivateDirectoryWithin } from "./paths";
16
+
17
+ const sourceDeclaration = z.strictObject({ engine: StudioEngineSchema,
18
+ entrypoint: z.strictObject({ kind: z.enum(["python", "blend"]), path: StudioPathSchema }),
19
+ files: z.array(StudioPathSchema).min(1).max(512),
20
+ });
21
+
22
+ export async function executeStudioCommand(application: ApplicationContext, command: StudioCommand, signal: AbortSignal): Promise<unknown> {
23
+ const fence = async () => { await application.hostResourceLease?.assertOwned(); };
24
+ if (signal.aborted) throw new CliError("cancelled", "Studio command cancelled.");
25
+ if (command.action === "assets") {
26
+ const storageRoot = await ensurePhysicalPrivateDirectoryWithin(application.paths.repositoryRoot, "artifacts/slopcamera/generated/studio-assets/poly-haven");
27
+ const service = createPolyHavenAssetService({ storageRoot, signal, beforePublication: fence });
28
+ if (command.operation === "describe") return await service.describe(command.path);
29
+ const input = await readSpatialJson(resolve(application.paths.repositoryRoot, command.path), 16 * 1024 * 1024);
30
+ return command.operation === "search" ? await service.search(input) : command.operation === "plan" ? await service.plan(input) : await service.importAsset(input);
31
+ }
32
+ if (command.action === "init") return await createStudioScaffold(resolve(application.paths.repositoryRoot, command.path), command.template, fence);
33
+ if (command.action === "bundle") {
34
+ const manifest = resolve(application.paths.repositoryRoot, command.path), declaration = sourceDeclaration.parse(await readSpatialJson(manifest, 1024 * 1024));
35
+ const sourceRoot = command.sourceRoot === undefined ? dirname(manifest) : resolve(application.paths.repositoryRoot, command.sourceRoot);
36
+ const bundle = await captureStudioSource({ ...declaration, sourceRoot });
37
+ const studioRoot = await studioStorageRoot(application), retained = await retainStudioSource({ studioRoot, sourceRoot, bundle, fence });
38
+ return { bundle: retained.bundle, bundleSha256: retained.bundleSha256,
39
+ manifest: relative(application.paths.repositoryRoot, resolve(retained.sourceRoot, "../bundle.json")), executed: false,
40
+ next: "Bind bundleSha256 into a new job ID after source edits, then use studio plan and studio run." };
41
+ }
42
+ const selection = { threads: 1, ...(command.action !== "probe" && command.action !== "run" ? {} : {
43
+ ...(command.blender === undefined ? {} : { blender: resolve(application.paths.repositoryRoot, command.blender) }),
44
+ ...(command.python === undefined ? {} : { python: resolve(application.paths.repositoryRoot, command.python) }),
45
+ }) };
46
+ const service = createStudioService({ application, selection });
47
+ if (command.action === "asset") return await admitStudioSpatialAsset({ application, service,
48
+ selection: { jobId: command.id, outputId: command.outputId, representation: command.representation, ...(command.frame === undefined ? {} : { frame: command.frame }) },
49
+ assetId: command.assetId, signal, beforePublication: fence });
50
+ if (command.action === "assemble") return await assembleStudioJob({ application, service, jobId: command.id, outputId: command.outputId, signal, beforePublication: fence, ...(command.title === undefined ? {} : { title: command.title }) });
51
+ if (command.action === "encode") return await encodeStudioSequence({ application, service, jobId: command.id, outputId: command.outputId, signal, beforePublication: fence });
52
+ if (command.action === "inspect") return await service.inspect(command.id);
53
+ if (command.action === "reconcile") return await service.reconcile(command.id, fence);
54
+ const job = await readSpatialJson(resolve(application.paths.repositoryRoot, command.path), 1024 * 1024);
55
+ if (command.action === "plan") return await service.plan(job);
56
+ if (command.action === "probe") return await service.bind(job, signal);
57
+ if (!command.allowTrustedCode) throw new CliError("authorization-required", "Studio execution requires explicit trusted-code authorization.");
58
+ return await service.run(job, { signal, allowTrustedCode: true, beforePublication: fence,
59
+ ...(application.hostResourceLease === undefined ? {} : { inheritedFileDescriptors: application.hostResourceLease.inheritedFileDescriptors }) });
60
+ }
@@ -0,0 +1,60 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import { createNodeBundleFileSystem } from "../core/storage";
4
+ import { withMutationLock } from "./mutation-lock";
5
+ import { ensurePhysicalPrivateDirectoryWithin } from "./paths";
6
+ import { studioJson } from "./studio-files";
7
+ import type { StudioProcessPort, StudioProcessResult } from "./studio-process";
8
+
9
+ /** A machine-wide durable marker outlives the CLI. Unsettled native work blocks later dispatch. */
10
+ export async function withStudioProcessCustody<T>(input: {
11
+ readonly machineStateRoot: string; readonly process: StudioProcessPort; readonly label: string;
12
+ readonly signal: AbortSignal; readonly fence: () => Promise<void>;
13
+ }, execute: (process: StudioProcessPort) => Promise<T>): Promise<T> {
14
+ const directory = await ensurePhysicalPrivateDirectoryWithin(input.machineStateRoot, "studio-native");
15
+ return await withMutationLock(directory, { command: "studio native", label: input.label }, async lease => {
16
+ const fs = createNodeBundleFileSystem(directory);
17
+ const fence = async () => { await input.fence(); await lease.assertOwned(); };
18
+ let current: unknown;
19
+ try { current = JSON.parse(await fs.readText("activity.json")) as unknown; }
20
+ catch (error) { if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error; }
21
+ if (current !== undefined) {
22
+ if (typeof current !== "object" || current === null || !("state" in current) || current.state !== "closed") throw new Error(`Unsettled native studio custody requires inspection: ${join(directory, "activity.json")}`);
23
+ }
24
+ let active = false;
25
+ const process: StudioProcessPort = {
26
+ run: async (argv, options) => {
27
+ if (active) throw new Error("One studio custody lease can dispatch only one native process at a time.");
28
+ active = true;
29
+ const attempt = randomUUID(), startedAt = new Date().toISOString();
30
+ let pid: number | undefined;
31
+ const marker = (state: "starting" | "running" | "closed" | "unknown", result?: StudioProcessResult) => studioJson({
32
+ kind: "slopcamera.studio-native-activity", schemaVersion: 1, attempt, label: input.label, startedAt, state,
33
+ ...(pid === undefined ? {} : { processGroup: pid }),
34
+ ...(result === undefined ? {} : { exitCode: result.exitCode, failure: result.failure ?? null, finishedAt: new Date().toISOString() }),
35
+ });
36
+ await fs.writeTextAtomicGuarded!("activity.json", marker("starting"), fence);
37
+ let result: StudioProcessResult;
38
+ try {
39
+ result = await input.process.run(argv, {
40
+ ...options,
41
+ onSpawn: async processId => {
42
+ pid = processId;
43
+ await fs.writeTextAtomicGuarded!("activity.json", marker("running"), fence);
44
+ await options.onSpawn?.(processId);
45
+ },
46
+ });
47
+ } catch (error) {
48
+ // An unexpected adapter exception cannot establish whether it dispatched native work.
49
+ await fs.writeTextAtomicGuarded!("activity.json", marker("unknown"), () => lease.assertOwned());
50
+ throw error;
51
+ }
52
+ // Terminal custody is evidence even when the caller's publication authority was cancelled.
53
+ await fs.writeTextAtomicGuarded!("activity.json", marker(result.custody), () => lease.assertOwned());
54
+ active = result.custody !== "closed";
55
+ return result;
56
+ },
57
+ };
58
+ return await execute(process);
59
+ });
60
+ }
@@ -0,0 +1,216 @@
1
+ import { createHash } from "node:crypto";
2
+ import { join, relative } from "node:path";
3
+ import { z } from "zod";
4
+ import { boundedCanonicalJsonSha256 } from "../../../src/code/canonical-json";
5
+ import { parseStudioPlan, STUDIO_LIMITS, validateStudioReceipt, type StudioPlan } from "../../../src/studio";
6
+ import { bindExactCapabilities, ExactCapabilityApplicationRunner, assertExactCapabilityExecutable, type ExactCapabilityBinding } from "../application/capability-binding";
7
+ import type { ApplicationContext } from "../application/context";
8
+ import type { MediaArtifactReference } from "../application/operations/media/shared";
9
+ import { StudioRunOutputSchema } from "../application/studio-port";
10
+ import { createNodeBundleFileSystem } from "../core/storage";
11
+ import { CliError } from "./errors";
12
+ import { withMutationLock } from "./mutation-lock";
13
+ import { ensurePhysicalPrivateDirectoryWithin } from "./paths";
14
+ import { withStudioProcessCustody } from "./studio-custody";
15
+ import { inventoryStudioFiles, readStudioFileEdges, studioJson } from "./studio-files";
16
+ import { NativeStudioProcess, studioChildEnvironment, type StudioProcessPort } from "./studio-process";
17
+ import { studioStorageRoot, type createStudioService } from "./studio-service";
18
+
19
+ const DOCUMENT_BYTES = 32 * 1024 * 1024;
20
+ const digest = (value: unknown) => boundedCanonicalJsonSha256(value, { maximumBytes: DOCUMENT_BYTES, maximumDepth: 48, maximumValues: 1_000_000 });
21
+ type Render = NonNullable<StudioPlan["job"]["render"]>;
22
+ type Profile = "rgb8-lossless-h264-v1" | "rgba8-lossless-qtrle-v1";
23
+
24
+ interface EncodeRequest {
25
+ readonly kind: "slopcamera.studio-encode-request"; readonly schemaVersion: 1;
26
+ readonly planSha256: string; readonly nativeReceipt: MediaArtifactReference;
27
+ readonly jobId: string; readonly outputId: string; readonly render: Render;
28
+ readonly frames: readonly { readonly path: string; readonly frame: number; readonly sha256: string; readonly bytes: number }[];
29
+ readonly profile: Profile;
30
+ readonly conversion: "identity-uint8" | "ffmpeg-no-dither-uint16-to-uint8-v1";
31
+ readonly colorSpace: "srgb"; readonly alpha: "opaque" | "straight";
32
+ readonly tools: readonly ExactCapabilityBinding[];
33
+ readonly limits: { readonly timeoutMs: number; readonly maximumOutputBytes: number };
34
+ }
35
+ export interface StudioEncodeReceipt {
36
+ readonly kind: "slopcamera.studio-encode-receipt"; readonly schemaVersion: 1;
37
+ readonly requestSha256: string; readonly request: EncodeRequest;
38
+ readonly artifact: MediaArtifactReference;
39
+ readonly verification: {
40
+ readonly frameCount: number; readonly canonicalFrameSha256: string;
41
+ readonly sourceFramehash: MediaArtifactReference; readonly videoFramehash: MediaArtifactReference;
42
+ readonly probe: MediaArtifactReference;
43
+ };
44
+ }
45
+ export interface StudioEncodeInput {
46
+ readonly application: ApplicationContext;
47
+ readonly service: Pick<ReturnType<typeof createStudioService>, "inspect">;
48
+ readonly jobId: string; readonly outputId: string; readonly signal: AbortSignal;
49
+ beforePublication(): Promise<void>;
50
+ readonly process?: StudioProcessPort;
51
+ }
52
+
53
+ /** Framehash streams carry hashes, never raw pixels; rows are checked without allocating a frame array. */
54
+ export function verifyStudioFramehash(text: string, render: Render, alpha: boolean): string {
55
+ const count = render.endFrameExclusive - render.startFrame;
56
+ const expectedBytes = render.width * render.height * (alpha ? 4 : 3);
57
+ let rows = 0, timeBase = false, dimensions = false, algorithm = false;
58
+ const hash = createHash("sha256");
59
+ for (const line of text.matchAll(/[^\r\n]+/gu)) {
60
+ const value = line[0].trim();
61
+ if (value.startsWith("#")) {
62
+ if (value === "#hash: SHA256") algorithm = true;
63
+ if (value.startsWith("#tb ")) {
64
+ const match = /^#tb 0: (\d+)\/(\d+)$/u.exec(value);
65
+ if (timeBase || match === null || BigInt(match[1]!) === 0n || BigInt(match[2]!) === 0n || BigInt(match[1]!) * BigInt(render.frameRate.numerator) !== BigInt(match[2]!) * BigInt(render.frameRate.denominator)) throw new CliError("invalid-data", "Encoded framehash clock differs from the source cadence.");
66
+ timeBase = true;
67
+ }
68
+ if (value.startsWith("#dimensions ")) {
69
+ if (dimensions || value !== `#dimensions 0: ${render.width}x${render.height}`) throw new CliError("invalid-data", "Encoded framehash dimensions differ.");
70
+ dimensions = true;
71
+ }
72
+ continue;
73
+ }
74
+ const match = /^(\d+),\s*(-?\d+),\s*(-?\d+),\s*(\d+),\s*(\d+),\s*([a-f0-9]{64})$/u.exec(value);
75
+ if (match === null || Number(match[1]) !== 0 || Number(match[2]) !== rows || Number(match[3]) !== rows || Number(match[4]) !== 1 || Number(match[5]) !== expectedBytes || rows >= count) throw new CliError("invalid-data", "Encoded frames have unexpected timing, size, or coverage.");
76
+ hash.update(`${rows}:${expectedBytes}:${match[6]}\n`);
77
+ rows++;
78
+ }
79
+ if (!algorithm || !timeBase || !dimensions || rows !== count) throw new CliError("invalid-data", "Encoded framehash is incomplete.");
80
+ return hash.digest("hex");
81
+ }
82
+
83
+ const probeSchema = z.object({ streams: z.array(z.object({
84
+ codec_type: z.string(), codec_name: z.string(), pix_fmt: z.string(), width: z.number().int(), height: z.number().int(),
85
+ avg_frame_rate: z.string(), time_base: z.string(), start_pts: z.number().int().safe(), duration_ts: z.number().int().safe(), nb_read_frames: z.string(),
86
+ color_range: z.string().optional(), color_space: z.string().optional(), color_transfer: z.string().optional(), color_primaries: z.string().optional(),
87
+ })).length(1) });
88
+ function verifyProbe(text: string, request: EncodeRequest): void {
89
+ const stream = probeSchema.parse(JSON.parse(text) as unknown).streams[0]!;
90
+ const r = request.render, count = r.endFrameExclusive - r.startFrame;
91
+ const ratio = (value: string) => { const match = /^(\d+)\/(\d+)$/u.exec(value); if (match === null || match[2] === "0") throw new CliError("invalid-data", "Invalid encoded media clock."); return [BigInt(match[1]!), BigInt(match[2]!)] as const; };
92
+ const fps = ratio(stream.avg_frame_rate), tb = ratio(stream.time_base);
93
+ const opaque = request.alpha === "opaque";
94
+ if (stream.codec_type !== "video" || stream.codec_name !== (opaque ? "h264" : "qtrle") || stream.pix_fmt !== (opaque ? "gbrp" : "argb")
95
+ || stream.width !== r.width || stream.height !== r.height || stream.nb_read_frames !== String(count) || stream.start_pts !== 0
96
+ || fps[0] * BigInt(r.frameRate.denominator) !== fps[1] * BigInt(r.frameRate.numerator)
97
+ || BigInt(stream.duration_ts) * tb[0] * BigInt(r.frameRate.numerator) !== BigInt(count) * BigInt(r.frameRate.denominator) * tb[1]
98
+ || opaque && (stream.color_range !== "pc" || stream.color_space !== "gbr" || stream.color_transfer !== "iec61966-2-1" || stream.color_primaries !== "bt709")) throw new CliError("invalid-data", "Encoded video differs from its declared clock, pixels, codec, or color profile.", { observed: stream, render: r });
99
+ }
100
+
101
+ /** Fixed media conversion only: this operation never executes the retained authored source. */
102
+ export async function encodeStudioSequence(input: StudioEncodeInput): Promise<{ readonly artifact: MediaArtifactReference; readonly receipt: MediaArtifactReference; readonly document: StudioEncodeReceipt }> {
103
+ const { application } = input;
104
+ if (!/^studio_[a-zA-Z0-9][a-zA-Z0-9_-]{0,120}$/u.test(input.jobId)) throw new CliError("invalid-data", "Invalid studio job ID.");
105
+ if (application.hostResourceLease === undefined || application.machineStateRoot === undefined) throw new CliError("unavailable", "Studio encoding requires admitted host resources and machine process custody.");
106
+ const hostLease = application.hostResourceLease;
107
+ const initialFence = async () => { if (input.signal.aborted) throw new CliError("cancelled", "Studio encoding was cancelled."); await hostLease.assertOwned(); await input.beforePublication(); if (input.signal.aborted) throw new CliError("cancelled", "Studio encoding was cancelled."); };
108
+ await initialFence();
109
+ const native = StudioRunOutputSchema.parse(await input.service.inspect(input.jobId));
110
+ const root = await studioStorageRoot(application), jobRoot = join(root, "jobs", input.jobId);
111
+ const jobFs = createNodeBundleFileSystem(jobRoot);
112
+ const plan = parseStudioPlan(JSON.parse(await jobFs.readText("plan.json", DOCUMENT_BYTES)) as unknown);
113
+ const original = validateStudioReceipt({ plan, receipt: native.document });
114
+ if (original.state !== "succeeded" || original.jobId !== input.jobId) throw new CliError("invalid-data", "Only successful retained studio jobs can be encoded.");
115
+ const repositoryFs = createNodeBundleFileSystem(application.paths.repositoryRoot);
116
+ const nativePath = relative(application.paths.repositoryRoot, join(jobRoot, "receipt.json"));
117
+ const actualReceipt = await repositoryFs.inspectFile!(nativePath, DOCUMENT_BYTES);
118
+ if (native.receipt.path !== nativePath || native.receipt.bytes !== actualReceipt.bytes || native.receipt.sha256 !== actualReceipt.sha256
119
+ || studioJson(original) !== studioJson(JSON.parse(await jobFs.readText("receipt.json", DOCUMENT_BYTES)) as unknown)) throw new CliError("conflict", "Native studio receipt identity changed.");
120
+ const output = plan.job.outputs.find(item => item.id === input.outputId), render = plan.job.render;
121
+ if (output === undefined || output.kind !== "sequence" || output.format !== "png" || output.interpretation.kind !== "raster" || render === undefined) throw new CliError("unsupported-plan", "Studio encode requires a declared PNG frame sequence.");
122
+ const raster = output.interpretation, alpha = raster.alpha === "straight";
123
+ if (raster.semantic !== "color" || raster.colorSpace !== "srgb" || !["opaque", "straight"].includes(raster.alpha)
124
+ || !["uint8", "uint16"].includes(raster.dataType) || raster.channels.join(",") !== (alpha ? "R,G,B,A" : "R,G,B")) throw new CliError("unsupported-plan", "Studio encode supports only explicit sRGB RGB or straight RGBA integer color sequences.");
125
+ const frames = original.outputs.filter(item => item.outputId === input.outputId).sort((a, b) => a.frame! - b.frame!).map(item => ({ path: item.path, frame: item.frame!, sha256: item.sha256, bytes: item.bytes }));
126
+ const sourceFs = createNodeBundleFileSystem(join(jobRoot, "outputs"));
127
+ const verifySource = async () => {
128
+ await initialFence();
129
+ if (studioJson(await input.service.inspect(input.jobId)) !== studioJson(native)
130
+ || studioJson(parseStudioPlan(JSON.parse(await jobFs.readText("plan.json", DOCUMENT_BYTES)) as unknown)) !== studioJson(plan)) throw new CliError("conflict", "Native studio source provenance changed during encoding.");
131
+ for (const frame of frames) {
132
+ const found = await sourceFs.inspectFile!(frame.path, frame.bytes);
133
+ const { first } = await readStudioFileEdges(join(jobRoot, "outputs", frame.path), frame.bytes, 33);
134
+ if (found.bytes !== frame.bytes || found.sha256 !== frame.sha256 || first.length < 33 || first.subarray(0, 8).toString("hex") !== "89504e470d0a1a0a"
135
+ || first.toString("ascii", 12, 16) !== "IHDR" || first.readUInt32BE(16) !== render.width || first.readUInt32BE(20) !== render.height
136
+ || first[24] !== (raster.dataType === "uint16" ? 16 : 8) || first[25] !== (alpha ? 6 : 2)) throw new CliError("conflict", "Native PNG bytes or sample layout differ from the retained declaration.");
137
+ }
138
+ };
139
+ await verifySource();
140
+ const tools = await bindExactCapabilities(application, ["ffmpeg", "ffprobe"]);
141
+ const request: EncodeRequest = {
142
+ kind: "slopcamera.studio-encode-request", schemaVersion: 1, planSha256: plan.planSha256, nativeReceipt: native.receipt,
143
+ jobId: input.jobId, outputId: input.outputId, render, frames,
144
+ profile: alpha ? "rgba8-lossless-qtrle-v1" : "rgb8-lossless-h264-v1",
145
+ conversion: raster.dataType === "uint16" ? "ffmpeg-no-dither-uint16-to-uint8-v1" : "identity-uint8", colorSpace: "srgb", alpha: alpha ? "straight" : "opaque", tools,
146
+ limits: { timeoutMs: Math.min(plan.job.limits.timeoutSeconds * 1000, 300_000), maximumOutputBytes: Math.min(plan.job.limits.maximumOutputBytes, 512 * 1024 * 1024) },
147
+ };
148
+ const requestSha256 = digest(request);
149
+ const directory = await ensurePhysicalPrivateDirectoryWithin(jobRoot, `derivatives/${requestSha256}`);
150
+ return await withMutationLock(directory, { command: "studio encode", label: input.jobId }, async lease => {
151
+ const deadline = performance.now() + request.limits.timeoutMs;
152
+ const fence = async () => { await initialFence(); await lease.assertOwned(); if (performance.now() >= deadline) throw new CliError("cancelled", "Studio encode aggregate deadline exceeded."); };
153
+ const fs = createNodeBundleFileSystem(directory);
154
+ const optional = async (path: string) => { try { return await fs.readText(path, DOCUMENT_BYTES); } catch (error) { if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined; throw error; } };
155
+ const intent = studioJson({ kind: "slopcamera.studio-encode-intent", schemaVersion: 1, requestSha256, request });
156
+ const previous = await optional("intent.json");
157
+ if (previous !== undefined && previous !== intent) throw new CliError("conflict", "Retained studio encode intent differs from verified source and settings.");
158
+ const video = alpha ? "video.mov" : "video.mp4";
159
+ let existingVideo: { readonly bytes: number; readonly sha256: string } | undefined;
160
+ try { existingVideo = await fs.inspectFile!(video, request.limits.maximumOutputBytes); }
161
+ catch (error) { if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error; }
162
+ if (previous === undefined && existingVideo !== undefined) throw new CliError("conflict", "A studio video without its exact intent cannot be adopted.");
163
+ if (previous !== undefined && existingVideo === undefined) throw new CliError("ambiguous", "Interrupted studio encoding has no verifiable video. The retained intent cannot authorize another encode.");
164
+ if (previous === undefined) await fs.writeTextNoReplace!("intent.json", intent, fence);
165
+ for (const child of ["work/home", "work/tmp", "work/blender-user"]) await ensurePhysicalPrivateDirectoryWithin(directory, child);
166
+ const artifact = async (path: string, maximumBytes = DOCUMENT_BYTES): Promise<MediaArtifactReference> => ({ path: relative(application.paths.repositoryRoot, join(directory, path)), ...await fs.inspectFile!(path, maximumBytes) });
167
+ const retainText = async (path: string, text: string) => { const prior = await optional(path); if (prior !== undefined && prior !== text) throw new CliError("conflict", `Retained studio encode evidence differs: ${path}`); if (prior === undefined) await fs.writeTextNoReplace!(path, text, fence); return await artifact(path); };
168
+ const assertBudget = async () => { await fence(); await inventoryStudioFiles(directory, 64, request.limits.maximumOutputBytes + 4 * DOCUMENT_BYTES); };
169
+ return await withStudioProcessCustody({ machineStateRoot: application.machineStateRoot!, process: input.process ?? new NativeStudioProcess(), label: `encode ${input.jobId}/${input.outputId}`, signal: input.signal, fence }, async supervised => {
170
+ const runner = new ExactCapabilityApplicationRunner({ run: async (argv, options) => {
171
+ await fence();
172
+ const result = await supervised.run(argv, { cwd: directory, env: studioChildEnvironment(argv[0], join(directory, "work"), 1), timeoutMs: Math.max(1, Math.floor(deadline - performance.now())), maximumLogBytes: options?.maxOutputBytes ?? 65_536,
173
+ signal: input.signal, inheritedFileDescriptors: hostLease.inheritedFileDescriptors, assertBudget });
174
+ if (result.custody !== "closed" || result.failure !== undefined || result.exitCode !== 0) throw new CliError(result.custody !== "closed" ? "ambiguous" : input.signal.aborted ? "cancelled" : "subprocess", `Studio encode process failed (${result.failure ?? "exit"}); retained intent and output remain available for inspection.`);
175
+ if (result.stderr.trim() !== "") throw new CliError("invalid-data", "Studio encoder reported unexpected diagnostics; the derivative was not published.");
176
+ return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
177
+ } }, tools, application.paths.privateRoot);
178
+ const ffmpeg = tools.find(tool => tool.name === "ffmpeg")!.command, ffprobe = tools.find(tool => tool.name === "ffprobe")!.command;
179
+ const count = render.endFrameExclusive - render.startFrame, rate = `${render.frameRate.numerator}/${render.frameRate.denominator}`, timeBase = `${render.frameRate.denominator}/${render.frameRate.numerator}`;
180
+ const format = alpha ? "rgba" : "rgb24";
181
+ const base = ["-v", "error", "-nostdin", "-threads", "1", "-filter_threads", "1", "-protocol_whitelist", "file,pipe", "-max_pixels", String(STUDIO_LIMITS.pixels)];
182
+ const source = ["-f", "image2", "-framerate", rate, "-start_number", String(render.startFrame), "-start_number_range", "1", "-pattern_type", "sequence", "-i", `${join(jobRoot, "outputs").replaceAll("%", "%%")}/${output.pathPattern}`];
183
+ const videoInput = ["-format_whitelist", "mov", "-enable_drefs", "0", "-use_absolute_path", "0"];
184
+ // PNGs need not carry color tags. Apply the retained authored interpretation
185
+ // to decoded frames as well as the encoder; x264 otherwise inherits unknowns.
186
+ const conversion = ["-map", "0:v:0", "-frames:v", String(count), "-an", "-sws_flags", "accurate_rnd+bitexact+full_chroma_int", "-sws_dither", "none", "-vf", `format=${format},setparams=range=full:color_primaries=bt709:color_trc=iec61966-2-1:colorspace=gbr`, "-fps_mode", "passthrough", "-enc_time_base", timeBase];
187
+ const hashArgs = ["-c:v", "rawvideo", "-threads:v", "1", "-pix_fmt", format, "-f", "framehash", "-hash", "sha256", "pipe:1"];
188
+ const logLimit = Math.min(4_194_304, count * 160 + 16_384);
189
+ const sourceText = (await runner.run([ffmpeg, ...base, ...source, ...conversion, ...hashArgs], { maxOutputBytes: logLimit })).stdout;
190
+ const canonicalFrameSha256 = verifyStudioFramehash(sourceText, render, alpha);
191
+ const sourceFramehash = await retainText("source.framehash", sourceText);
192
+ if (existingVideo === undefined) {
193
+ await verifySource();
194
+ await runner.run([ffmpeg, ...base, ...source, ...conversion,
195
+ ...(alpha ? ["-c:v", "qtrle", "-pix_fmt", "argb"] : ["-c:v", "libx264rgb", "-preset", "veryfast", "-crf", "0", "-bf", "0", "-pix_fmt", "rgb24"]),
196
+ "-threads:v", "1", "-color_range", "pc", "-color_primaries", "bt709", "-color_trc", "iec61966-2-1", "-colorspace", "rgb", "-video_track_timescale", String(render.frameRate.numerator),
197
+ "-avoid_negative_ts", "disabled", "-fs", String(request.limits.maximumOutputBytes), "-f", alpha ? "mov" : "mp4", "-n", join(directory, video)]);
198
+ }
199
+ const before = await artifact(video, request.limits.maximumOutputBytes);
200
+ const probeText = (await runner.run([ffprobe, "-v", "error", "-threads", "1", "-protocol_whitelist", "file", "-max_pixels", String(STUDIO_LIMITS.pixels), ...videoInput, "-count_frames", "-show_entries", "stream=codec_type,codec_name,pix_fmt,width,height,avg_frame_rate,time_base,start_pts,duration_ts,nb_read_frames,color_range,color_space,color_transfer,color_primaries", "-of", "json", join(directory, video)], { maxOutputBytes: 65_536 })).stdout;
201
+ const probe = await retainText("probe.json", probeText);
202
+ verifyProbe(probeText, request);
203
+ const videoText = (await runner.run([ffmpeg, ...base, ...videoInput, "-i", join(directory, video), ...conversion, ...hashArgs], { maxOutputBytes: logLimit })).stdout;
204
+ if (verifyStudioFramehash(videoText, render, alpha) !== canonicalFrameSha256) throw new CliError("invalid-data", "Decoded studio video pixels differ from the canonical source frames.");
205
+ await verifySource();
206
+ for (const tool of tools) await assertExactCapabilityExecutable(tool);
207
+ if (studioJson(before) !== studioJson(await artifact(video, request.limits.maximumOutputBytes))) throw new CliError("conflict", "Studio video changed during verification.");
208
+ const document: StudioEncodeReceipt = { kind: "slopcamera.studio-encode-receipt", schemaVersion: 1, requestSha256, request, artifact: before,
209
+ verification: { frameCount: count, canonicalFrameSha256, sourceFramehash, videoFramehash: await retainText("video.framehash", videoText), probe } };
210
+ await assertBudget();
211
+ const receipt = await retainText("receipt.json", studioJson(document));
212
+ await fence();
213
+ return { artifact: before, receipt, document };
214
+ });
215
+ });
216
+ }
@@ -0,0 +1,52 @@
1
+ /** Bounded flat single-part EXR header inspection; actual sample decoding remains FFprobe's job. */
2
+ export function inspectStudioExrHeader(bytes: Buffer): {
3
+ readonly width: number; readonly height: number; readonly channels: readonly { readonly name: string; readonly dataType: "uint32" | "float16" | "float32" }[];
4
+ } {
5
+ const require = (condition: boolean) => { if (!condition) throw new Error("EXR exceeds the admitted flat single-part header profile."); };
6
+ require(bytes.length >= 16 && bytes.readUInt32LE(0) === 20_000_630 && (bytes.readUInt32LE(4) & 0xff) === 2);
7
+ require((bytes.readUInt32LE(4) & ~0x602) === 0); // Allow tiled/long-name flags; deep and multipart need separate profiles.
8
+ let offset = 8;
9
+ const string = (limit: number) => {
10
+ const end = bytes.indexOf(0, offset);
11
+ require(end >= offset && end < limit && end - offset <= 255);
12
+ const value = new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(offset, end));
13
+ offset = end + 1;
14
+ return value;
15
+ };
16
+ const attributes = new Set<string>();
17
+ let width: number | undefined, height: number | undefined;
18
+ let channels: { name: string; dataType: "uint32" | "float16" | "float32" }[] | undefined;
19
+ while (true) {
20
+ const name = string(bytes.length);
21
+ if (name === "") break;
22
+ require(attributes.size < 256 && !attributes.has(name)); attributes.add(name);
23
+ const type = string(bytes.length);
24
+ require(offset + 4 <= bytes.length);
25
+ const size = bytes.readUInt32LE(offset); offset += 4;
26
+ const end = offset + size;
27
+ require(end <= bytes.length);
28
+ if (name === "dataWindow") {
29
+ require(type === "box2i" && size === 16);
30
+ width = bytes.readInt32LE(offset + 8) - bytes.readInt32LE(offset) + 1;
31
+ height = bytes.readInt32LE(offset + 12) - bytes.readInt32LE(offset + 4) + 1;
32
+ require(width > 0 && height > 0 && width <= 8192 && height <= 8192 && width * height <= 33_554_432);
33
+ } else if (name === "channels") {
34
+ require(type === "chlist"); channels = [];
35
+ const names = new Set<string>();
36
+ while (true) {
37
+ const channel = string(end);
38
+ if (channel === "") break;
39
+ require(channels.length < 32 && !names.has(channel) && offset + 16 <= end);
40
+ names.add(channel);
41
+ const pixelType = bytes.readInt32LE(offset);
42
+ require(pixelType >= 0 && pixelType <= 2 && bytes.readInt32LE(offset + 8) === 1 && bytes.readInt32LE(offset + 12) === 1);
43
+ channels.push({ name: channel, dataType: (["uint32", "float16", "float32"] as const)[pixelType]! });
44
+ offset += 16;
45
+ }
46
+ require(offset === end && channels.length > 0);
47
+ }
48
+ offset = end;
49
+ }
50
+ require(width !== undefined && height !== undefined && channels !== undefined);
51
+ return { width: width!, height: height!, channels: channels! };
52
+ }