@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,200 @@
1
+ import { Effect, Exit, Option, Ref } from "effect";
2
+ import { ProjectAssetRoleSchema } from "../contracts";
3
+ import {
4
+ operationFinally, operationResource, operationValidation, type OperationEffectFailure,
5
+ } from "../application/operation-effects";
6
+ import { CliError } from "./errors";
7
+ import type { ProcessRunner } from "./io";
8
+ import {
9
+ MAX_MEDIA_BYTES, importedAsset, parseMediaProbe,
10
+ type IngestedProjectMedia, type IngestProjectMediaOptions, type ProbedMedia, type StagedMediaImport,
11
+ } from "./media-ingest-model";
12
+ import { MediaIngestPlatform, type MediaIngestPlatformService } from "./media-ingest-platform";
13
+
14
+ type Program<A> = Effect.Effect<A, OperationEffectFailure, MediaIngestPlatform>;
15
+ type FileIdentity = StagedMediaImport["identity"];
16
+
17
+ function hasCode(failure: OperationEffectFailure, code: string): boolean {
18
+ return failure.cause instanceof Error && "code" in failure.cause && failure.cause.code === code;
19
+ }
20
+
21
+ function privateImportsDirectory(projectDirectory: string): Program<string> {
22
+ return Effect.gen(function*() {
23
+ const platform = yield* MediaIngestPlatform;
24
+ yield* platform.ensurePrivateDirectory(projectDirectory);
25
+ const root = yield* platform.realpath(projectDirectory);
26
+ const imports = yield* platform.importsPath(root);
27
+ const created = yield* platform.lstat(imports).pipe(
28
+ Effect.flatMap(details => operationValidation("media", () => {
29
+ if (details.isSymbolicLink() || !details.isDirectory()) {
30
+ throw new CliError("unsafe-path", `Project imports path is not a physical directory: ${imports}`);
31
+ }
32
+ return false;
33
+ })),
34
+ Effect.catchAll(failure => hasCode(failure, "ENOENT")
35
+ ? Effect.as(platform.mkdir(imports), true)
36
+ : Effect.fail(failure)),
37
+ );
38
+ const actual = yield* platform.realpath(imports);
39
+ yield* platform.assertWithin(root, actual, "Project imports directory escaped its project.");
40
+ if (created) yield* platform.syncDirectory(root);
41
+ return actual;
42
+ });
43
+ }
44
+
45
+ /** A substituted/ambiguous path is retained, never treated as owned cleanup. */
46
+ function removeStagedImport(
47
+ platform: MediaIngestPlatformService,
48
+ temporary: string,
49
+ imports: string,
50
+ identity: FileIdentity,
51
+ ): Effect.Effect<void, OperationEffectFailure> {
52
+ return Effect.uninterruptible(Effect.gen(function*() {
53
+ const details = yield* platform.lstat(temporary).pipe(
54
+ Effect.map(Option.some),
55
+ Effect.catchAll(failure => hasCode(failure, "ENOENT") ? Effect.succeed(Option.none()) : Effect.fail(failure)),
56
+ );
57
+ if (Option.isSome(details)) {
58
+ yield* operationValidation("cleanup", () => {
59
+ if (!details.value.isFile() || details.value.isSymbolicLink()
60
+ || details.value.dev !== identity.dev || details.value.ino !== identity.ino) {
61
+ throw new CliError("conflict", "Staged media identity changed before cleanup; retaining the unowned entry.");
62
+ }
63
+ });
64
+ yield* platform.remove(temporary);
65
+ }
66
+ yield* platform.syncDirectory(imports);
67
+ }));
68
+ }
69
+
70
+ function stageImport(projectDirectory: string, sourcePath: string): Program<StagedMediaImport> {
71
+ return Effect.gen(function*() {
72
+ const platform = yield* MediaIngestPlatform;
73
+ const source = yield* platform.sourcePath(sourcePath);
74
+ const before = yield* platform.lstat(source).pipe(Effect.catchAll(() => operationValidation("media", () => {
75
+ throw new CliError("not-found", `Media source does not exist: ${sourcePath}`);
76
+ })));
77
+ yield* operationValidation("media", () => {
78
+ if (before.isSymbolicLink() || !before.isFile()) {
79
+ throw new CliError("unsafe-path", `Media source must be a physical regular file: ${sourcePath}`);
80
+ }
81
+ if (before.size <= 0 || before.size > MAX_MEDIA_BYTES) {
82
+ throw new CliError("invalid-data", `Media source must contain 1 through ${MAX_MEDIA_BYTES} bytes.`);
83
+ }
84
+ });
85
+ const imports = yield* privateImportsDirectory(projectDirectory);
86
+ const temporary = yield* platform.temporaryPath(imports, yield* platform.uniqueName());
87
+ const owned = yield* Ref.make(Option.none<FileIdentity>());
88
+ const staged = yield* Ref.make(Option.none<StagedMediaImport>());
89
+ const removeOwned = Effect.gen(function*() {
90
+ const identity = yield* Ref.get(owned);
91
+ if (Option.isSome(identity)) yield* removeStagedImport(platform, temporary, imports, identity.value);
92
+ });
93
+ const result = yield* Effect.exit(operationResource(
94
+ platform.openSource(source),
95
+ sourceHandle => Effect.gen(function*() {
96
+ const prepared = yield* Effect.exit(Effect.gen(function*() {
97
+ const opened = yield* platform.stat(sourceHandle);
98
+ yield* operationValidation("media", () => {
99
+ if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size) {
100
+ throw new CliError("conflict", "Media source changed before it was imported.");
101
+ }
102
+ });
103
+ const copied = yield* operationResource(
104
+ platform.openTemporary(temporary),
105
+ temporaryHandle => Effect.gen(function*() {
106
+ const identity = yield* platform.stat(temporaryHandle);
107
+ yield* operationValidation("media", () => {
108
+ if (!identity.isFile()) throw new CliError("unsafe-path", "Staged media must be a physical regular file.");
109
+ });
110
+ yield* Ref.set(owned, Option.some({ dev: identity.dev, ino: identity.ino }));
111
+ const copy = yield* platform.copy(sourceHandle, temporaryHandle);
112
+ const after = yield* platform.stat(sourceHandle);
113
+ yield* operationValidation("media", () => {
114
+ if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size
115
+ || copy.bytes !== opened.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs) {
116
+ throw new CliError("conflict", "Media source changed while it was being imported.");
117
+ }
118
+ });
119
+ // The original descriptor is still open: never reopen the staged path for fsync.
120
+ yield* platform.syncFile(temporaryHandle, temporary);
121
+ return { ...copy, identity: { dev: identity.dev, ino: identity.ino } };
122
+ }),
123
+ handle => platform.close(handle),
124
+ );
125
+ return {
126
+ absolutePath: yield* platform.destinationPath(imports, copied.sha256),
127
+ bytes: copied.bytes, sha256: copied.sha256, identity: copied.identity,
128
+ temporaryPath: temporary, importsDirectory: imports,
129
+ };
130
+ }));
131
+ if (Exit.isFailure(prepared)) return yield* operationFinally(prepared, removeOwned);
132
+ yield* Ref.set(staged, Option.some(prepared.value));
133
+ return prepared.value;
134
+ }),
135
+ handle => platform.close(handle),
136
+ ));
137
+ if (Exit.isFailure(result) && Option.isSome(yield* Ref.get(staged))) {
138
+ // A source-close failure cannot strand an otherwise completed stage. Keep
139
+ // that close failure public, even if compensating cleanup also fails.
140
+ yield* operationFinally(removeOwned, result);
141
+ }
142
+ return yield* result;
143
+ });
144
+ }
145
+
146
+ export function probeProjectMediaProgram(ffprobe: string, runner: ProcessRunner, path: string): Program<ProbedMedia> {
147
+ return Effect.gen(function*() {
148
+ const platform = yield* MediaIngestPlatform;
149
+ const result = yield* platform.probe(ffprobe, runner, path);
150
+ return yield* operationValidation("media", () => {
151
+ if (result.exitCode !== 0) {
152
+ throw new CliError("subprocess", `FFprobe could not inspect imported media: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
153
+ }
154
+ return parseMediaProbe(result.stdout);
155
+ });
156
+ });
157
+ }
158
+
159
+ function commitImport(imported: StagedMediaImport): Program<boolean> {
160
+ return Effect.uninterruptible(Effect.gen(function*() {
161
+ const platform = yield* MediaIngestPlatform;
162
+ return yield* Effect.gen(function*() {
163
+ yield* platform.link(imported.temporaryPath, imported.absolutePath);
164
+ yield* platform.syncDirectory(imported.importsDirectory);
165
+ return true;
166
+ }).pipe(Effect.catchAll(failure => {
167
+ if (!hasCode(failure, "EEXIST")) return Effect.fail(failure);
168
+ return platform.verify(imported.absolutePath, imported, `Content-addressed media ${imported.sha256}`).pipe(
169
+ Effect.catchAll(verificationFailure => verificationFailure.cause instanceof CliError && verificationFailure.cause.code === "unsafe-path"
170
+ ? Effect.fail(verificationFailure)
171
+ : operationValidation("media", () => { throw new CliError("conflict", `Content-addressed media collision for ${imported.sha256}.`); })),
172
+ Effect.as(false),
173
+ );
174
+ }));
175
+ }));
176
+ }
177
+
178
+ export function ingestProjectMediaProgram(options: IngestProjectMediaOptions): Program<IngestedProjectMedia> {
179
+ return Effect.gen(function*() {
180
+ const platform = yield* MediaIngestPlatform;
181
+ const role = yield* operationValidation("media", () => ProjectAssetRoleSchema.parse(options.role));
182
+ const [repositoryRoot, projectDirectory] = yield* Effect.all([
183
+ platform.realpath(options.repositoryRoot), platform.realpath(options.projectDirectory),
184
+ ], { concurrency: "unbounded" }).pipe(Effect.catchAll(() => operationValidation("media", () => {
185
+ throw new CliError("not-found", "Project media import requires existing repository and project directories.");
186
+ })));
187
+ yield* platform.assertWithin(repositoryRoot, projectDirectory, "Project media import directory is outside the repository.");
188
+ return yield* operationResource(
189
+ stageImport(projectDirectory, options.sourcePath),
190
+ imported => Effect.gen(function*() {
191
+ const probe = yield* probeProjectMediaProgram(options.ffprobe, options.runner, imported.temporaryPath);
192
+ const location = yield* platform.assetLocation(repositoryRoot, imported.absolutePath, options.sourcePath);
193
+ const asset = yield* operationValidation("media", () => importedAsset(options, role, imported, probe, location.path, location.label));
194
+ const created = yield* commitImport(imported);
195
+ return { absolutePath: imported.absolutePath, asset, created };
196
+ }),
197
+ imported => removeStagedImport(platform, imported.temporaryPath, imported.importsDirectory, imported.identity),
198
+ );
199
+ });
200
+ }
@@ -0,0 +1,30 @@
1
+ import { Effect } from "effect";
2
+ import { runStandaloneOperation, type OperationEffectFailure } from "../application/operation-effects";
3
+ import type { ProcessRunner } from "./io";
4
+ import type { IngestedProjectMedia, IngestProjectMediaOptions, ProbedMedia } from "./media-ingest-model";
5
+ import { MediaIngestPlatformLive } from "./media-ingest-platform";
6
+ import { ingestProjectMediaProgram, probeProjectMediaProgram } from "./media-ingest-program";
7
+
8
+ export {
9
+ parseMediaProbe, SELF_CONTAINED_MEDIA_INPUT_ARGUMENTS,
10
+ type IngestedProjectMedia, type IngestProjectMediaOptions,
11
+ type ProbedMedia, type ProbedMediaStream,
12
+ } from "./media-ingest-model";
13
+ export type { MediaIngestDurability } from "./media-ingest-platform";
14
+
15
+ /** Native composition beneath the application's existing operation owner. */
16
+ export function ingestProjectMediaEffect(options: IngestProjectMediaOptions): Effect.Effect<IngestedProjectMedia, OperationEffectFailure> {
17
+ return ingestProjectMediaProgram(options).pipe(Effect.provide(MediaIngestPlatformLive(options.durability)));
18
+ }
19
+
20
+ export async function ingestProjectMedia(options: IngestProjectMediaOptions): Promise<IngestedProjectMedia> {
21
+ return await runStandaloneOperation(ingestProjectMediaEffect(options));
22
+ }
23
+
24
+ export function probeProjectMediaEffect(ffprobe: string, runner: ProcessRunner, path: string): Effect.Effect<ProbedMedia, OperationEffectFailure> {
25
+ return probeProjectMediaProgram(ffprobe, runner, path).pipe(Effect.provide(MediaIngestPlatformLive()));
26
+ }
27
+
28
+ export async function probeProjectMedia(ffprobe: string, runner: ProcessRunner, path: string): Promise<ProbedMedia> {
29
+ return await runStandaloneOperation(probeProjectMediaEffect(ffprobe, runner, path));
30
+ }
@@ -0,0 +1,434 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { link, open, rm } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+
6
+ import {
7
+ AnalysisIdSchema,
8
+ AnalysisToolSchema,
9
+ MusicAnalysisV1Schema,
10
+ ProjectAnalysisReferenceSchema,
11
+ VideoProjectV1Schema,
12
+ type MusicAnalysisV1,
13
+ type ProjectAssetV1,
14
+ type VideoProjectV1,
15
+ } from "../contracts";
16
+ import {
17
+ analyzeMusic,
18
+ canonicalSlopcameraPersistenceDocument,
19
+ canonicalJson,
20
+ canonicalJsonSha256,
21
+ saveAnalysisArtifact,
22
+ saveVideoProject,
23
+ sha256Hex,
24
+ type MonoPcm,
25
+ } from "../core";
26
+ import { decodeAlignmentPcm, resolveAudioAnalysisSubject } from "./audio-analysis";
27
+ import { CliError } from "./errors";
28
+ import type { ProcessRunner } from "./io";
29
+ import { ensurePhysicalPrivateDirectoryWithin } from "./paths";
30
+ import { projectAnalysisPath, type OpenProject } from "./project-service";
31
+
32
+ export const MUSIC_ANALYSIS_SAMPLE_RATE_HZ = 8_000;
33
+ export const DEFAULT_MAX_DECODED_MUSIC_PCM_BYTES = 128 * 1024 * 1024;
34
+
35
+ const READ_BUFFER_BYTES = 256 * 1024;
36
+ const MAXIMUM_FEATURE_WINDOW_SAMPLES = 131_072;
37
+
38
+ export const DEFAULT_MUSIC_ANALYSIS_CONFIG = {
39
+ hopSize: 512,
40
+ minimumMusicUs: 1_000_000,
41
+ sampleRateHz: MUSIC_ANALYSIS_SAMPLE_RATE_HZ,
42
+ tempoWindowUs: 20_000_000,
43
+ windowSize: 2_048,
44
+ } as const satisfies MusicAnalysisV1["config"];
45
+
46
+ export type MusicProjectAnalysisReference = Extract<
47
+ VideoProjectV1["analyses"][number],
48
+ { readonly kind: "music" }
49
+ >;
50
+
51
+ type AudioStream = Extract<ProjectAssetV1["streams"][number], { readonly kind: "audio" }>;
52
+
53
+ export function assertCompleteMusicAnalysis(
54
+ analysisInput: MusicAnalysisV1,
55
+ stream: AudioStream,
56
+ ): MusicAnalysisV1 {
57
+ const analysis = MusicAnalysisV1Schema.parse(analysisInput);
58
+ const expectedEndUs = stream.segments.at(-1)?.assetRange.endUs ?? 0;
59
+ const toleranceSamples = Math.max(2, stream.segments.length * 2);
60
+ const toleranceUs = Math.ceil(toleranceSamples * 1_000_000 / analysis.config.sampleRateHz);
61
+ if (
62
+ expectedEndUs <= 0
63
+ || Math.abs(analysis.durationUs - expectedEndUs) > toleranceUs
64
+ ) {
65
+ throw new CliError(
66
+ "invalid-data",
67
+ `Music analysis ${analysis.analysisId} does not cover the complete audio-stream timeline.`,
68
+ { actualDurationUs: analysis.durationUs, expectedEndUs, toleranceUs },
69
+ );
70
+ }
71
+ return analysis;
72
+ }
73
+
74
+ export interface ReadMusicPcmOptions {
75
+ readonly maxBytes?: number;
76
+ readonly sampleRateHz?: number;
77
+ }
78
+
79
+ function validateReadBound(maxBytes: number): void {
80
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 4) {
81
+ throw new CliError("usage", "The decoded music PCM byte limit must be a safe integer of at least four.");
82
+ }
83
+ }
84
+
85
+ function validateMusicConfig(config: MusicAnalysisV1["config"]): MusicAnalysisV1["config"] {
86
+ for (const [label, value] of [
87
+ ["hopSize", config.hopSize],
88
+ ["windowSize", config.windowSize],
89
+ ] as const) {
90
+ if (!Number.isSafeInteger(value) || value <= 0 || value > MAXIMUM_FEATURE_WINDOW_SAMPLES) {
91
+ throw new CliError(
92
+ "usage",
93
+ `Music ${label} must be a positive safe integer no greater than ${MAXIMUM_FEATURE_WINDOW_SAMPLES}.`,
94
+ );
95
+ }
96
+ }
97
+ if (config.hopSize > config.windowSize) {
98
+ throw new CliError("usage", "Music analysis hopSize cannot exceed windowSize.");
99
+ }
100
+ if (!Number.isSafeInteger(config.minimumMusicUs) || config.minimumMusicUs < 0) {
101
+ throw new CliError("usage", "Music minimumMusicUs must be a nonnegative safe integer.");
102
+ }
103
+ if (!Number.isSafeInteger(config.tempoWindowUs) || config.tempoWindowUs <= 0) {
104
+ throw new CliError("usage", "Music tempoWindowUs must be a positive safe integer.");
105
+ }
106
+ if (config.sampleRateHz !== MUSIC_ANALYSIS_SAMPLE_RATE_HZ) {
107
+ throw new CliError(
108
+ "usage",
109
+ `Music analysis must use the decoder sample rate of ${MUSIC_ANALYSIS_SAMPLE_RATE_HZ} Hz.`,
110
+ );
111
+ }
112
+ return config;
113
+ }
114
+
115
+ /** Read little-endian float PCM incrementally, rejecting data beyond a hard byte cap. */
116
+ export async function readMonoFloat32PcmBounded(
117
+ path: string,
118
+ options: ReadMusicPcmOptions = {},
119
+ ): Promise<MonoPcm> {
120
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_DECODED_MUSIC_PCM_BYTES;
121
+ const sampleRateHz = options.sampleRateHz ?? MUSIC_ANALYSIS_SAMPLE_RATE_HZ;
122
+ validateReadBound(maxBytes);
123
+ if (!Number.isSafeInteger(sampleRateHz) || sampleRateHz < 8_000 || sampleRateHz > 192_000) {
124
+ throw new CliError("usage", "The decoded music PCM sample rate must be between 8,000 and 192,000 Hz.");
125
+ }
126
+
127
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
128
+ const samples: number[] = [];
129
+ const buffer = Buffer.allocUnsafe(READ_BUFFER_BYTES);
130
+ let carry = Buffer.alloc(0);
131
+ let totalBytes = 0;
132
+ try {
133
+ const details = await handle.stat();
134
+ if (!details.isFile()) throw new CliError("invalid-data", "Decoded music PCM is not a regular file.");
135
+ if (details.size > maxBytes) {
136
+ throw new CliError(
137
+ "invalid-data",
138
+ `Decoded music PCM exceeds its ${maxBytes}-byte resource bound.`,
139
+ { bytes: details.size, maxBytes },
140
+ );
141
+ }
142
+
143
+ while (true) {
144
+ const result = await handle.read(buffer, 0, buffer.length, null);
145
+ if (result.bytesRead === 0) break;
146
+ totalBytes += result.bytesRead;
147
+ if (totalBytes > maxBytes) {
148
+ throw new CliError(
149
+ "invalid-data",
150
+ `Decoded music PCM exceeds its ${maxBytes}-byte resource bound.`,
151
+ { bytes: totalBytes, maxBytes },
152
+ );
153
+ }
154
+ const bytes = carry.length === 0
155
+ ? buffer.subarray(0, result.bytesRead)
156
+ : Buffer.concat([carry, buffer.subarray(0, result.bytesRead)]);
157
+ const usableBytes = bytes.length - bytes.length % Float32Array.BYTES_PER_ELEMENT;
158
+ for (let offset = 0; offset < usableBytes; offset += Float32Array.BYTES_PER_ELEMENT) {
159
+ const sample = bytes.readFloatLE(offset);
160
+ if (!Number.isFinite(sample)) {
161
+ throw new CliError("invalid-data", "Decoded music PCM contains a non-finite sample.");
162
+ }
163
+ samples.push(sample);
164
+ }
165
+ carry = Buffer.from(bytes.subarray(usableBytes));
166
+ }
167
+ if (carry.length !== 0) {
168
+ throw new CliError("invalid-data", "Decoded music PCM ends with a partial float sample.");
169
+ }
170
+ if (samples.length === 0) throw new CliError("invalid-data", "Decoded music PCM is empty.");
171
+ return { sampleRateHz, samples };
172
+ } finally {
173
+ await handle.close();
174
+ }
175
+ }
176
+
177
+ export interface WithDecodedMusicPcmOptions {
178
+ readonly ffmpeg: string;
179
+ readonly maxDecodedPcmBytes?: number;
180
+ readonly projectDirectory: string;
181
+ readonly repositoryRoot: string;
182
+ readonly runner: ProcessRunner;
183
+ readonly stream: Parameters<typeof decodeAlignmentPcm>[4];
184
+ }
185
+
186
+ /** Decode one project audio stream and remove its temporary PCM on every callback exit path. */
187
+ export async function withDecodedMusicPcm<T>(
188
+ options: WithDecodedMusicPcmOptions,
189
+ callback: (pcm: MonoPcm) => Promise<T> | T,
190
+ ): Promise<T> {
191
+ const decodedPath = await decodeAlignmentPcm(
192
+ options.projectDirectory,
193
+ options.repositoryRoot,
194
+ options.ffmpeg,
195
+ options.runner,
196
+ options.stream,
197
+ options.maxDecodedPcmBytes ?? DEFAULT_MAX_DECODED_MUSIC_PCM_BYTES,
198
+ );
199
+ try {
200
+ const pcm = await readMonoFloat32PcmBounded(decodedPath, {
201
+ ...(options.maxDecodedPcmBytes === undefined ? {} : { maxBytes: options.maxDecodedPcmBytes }),
202
+ sampleRateHz: MUSIC_ANALYSIS_SAMPLE_RATE_HZ,
203
+ });
204
+ return await callback(pcm);
205
+ } finally {
206
+ await rm(decodedPath, { force: true });
207
+ }
208
+ }
209
+
210
+ export interface BuildMusicProjectUpdateOptions {
211
+ readonly analysis: MusicAnalysisV1;
212
+ readonly analysisPath: string;
213
+ readonly project: VideoProjectV1;
214
+ readonly updatedAt: string;
215
+ }
216
+
217
+ export function buildMusicAnalysisReference(
218
+ analysisInput: MusicAnalysisV1,
219
+ analysisPath: string,
220
+ ): MusicProjectAnalysisReference {
221
+ const analysis = MusicAnalysisV1Schema.parse(analysisInput);
222
+ const reference = ProjectAnalysisReferenceSchema.parse({
223
+ analysisId: analysis.analysisId,
224
+ assetId: analysis.subject.assetId,
225
+ createdAt: analysis.createdAt,
226
+ keyRegions: analysis.keyRegions.length,
227
+ kind: "music",
228
+ musicRegions: analysis.musicRegions.length,
229
+ path: analysisPath,
230
+ sha256: sha256Hex(`${canonicalJson(analysis)}\n`),
231
+ streamId: analysis.subject.streamId,
232
+ tempoRegions: analysis.tempoRegions.length,
233
+ });
234
+ if (reference.kind !== "music") {
235
+ throw new CliError("internal", "Music reference parsing changed its kind.");
236
+ }
237
+ return reference;
238
+ }
239
+
240
+ /** Build and validate the compact project reference for an immutable music sidecar. */
241
+ export function buildMusicProjectUpdate(options: BuildMusicProjectUpdateOptions): {
242
+ readonly project: VideoProjectV1;
243
+ readonly reference: MusicProjectAnalysisReference;
244
+ } {
245
+ const analysis = MusicAnalysisV1Schema.parse(options.analysis);
246
+ if (options.project.analyses.some(existing => existing.analysisId === analysis.analysisId)) {
247
+ throw new CliError("conflict", `Project analysis already exists: ${analysis.analysisId}`);
248
+ }
249
+ if (Date.parse(options.updatedAt) < Date.parse(options.project.updatedAt)) {
250
+ throw new CliError("conflict", "The music analysis update time cannot precede the project update time.");
251
+ }
252
+ const reference = buildMusicAnalysisReference(analysis, options.analysisPath);
253
+ const project = VideoProjectV1Schema.parse({
254
+ ...options.project,
255
+ analyses: [...options.project.analyses, reference],
256
+ updatedAt: options.updatedAt,
257
+ });
258
+ return { project, reference };
259
+ }
260
+
261
+ function isMissingFile(error: unknown): boolean {
262
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
263
+ }
264
+
265
+ async function requireUnusedAnalysisPath(project: OpenProject, path: string): Promise<void> {
266
+ try {
267
+ await project.fileSystem.readText(path);
268
+ } catch (error) {
269
+ if (isMissingFile(error)) return;
270
+ throw error;
271
+ }
272
+ throw new CliError("conflict", `Analysis artifact already exists and will not be overwritten: ${path}`);
273
+ }
274
+
275
+ async function syncDirectory(path: string): Promise<void> {
276
+ const handle = await open(path, "r");
277
+ try {
278
+ await handle.sync();
279
+ } finally {
280
+ await handle.close();
281
+ }
282
+ }
283
+
284
+ async function saveMusicAnalysisArtifactOnce(
285
+ project: OpenProject,
286
+ analysis: MusicAnalysisV1,
287
+ path: string,
288
+ ): Promise<void> {
289
+ const parentRelative = dirname(path);
290
+ const parent = await ensurePhysicalPrivateDirectoryWithin(project.directory.path, parentRelative);
291
+ const stagePath = `${path}.stage-${randomUUID()}`;
292
+ const absoluteStage = join(project.directory.path, stagePath);
293
+ const absoluteTarget = join(project.directory.path, path);
294
+ await saveAnalysisArtifact(project.fileSystem, analysis, stagePath);
295
+ try {
296
+ try {
297
+ await link(absoluteStage, absoluteTarget);
298
+ } catch (error) {
299
+ if (error instanceof Error && "code" in error && error.code === "EEXIST") {
300
+ throw new CliError(
301
+ "conflict",
302
+ `Analysis artifact already exists and will not be overwritten: ${path}`,
303
+ );
304
+ }
305
+ throw error;
306
+ }
307
+ } finally {
308
+ await rm(absoluteStage, { force: true });
309
+ }
310
+ await syncDirectory(parent);
311
+ }
312
+
313
+ export interface AnalyzeProjectMusicOptions {
314
+ readonly analysisId?: string;
315
+ readonly config?: MusicAnalysisV1["config"];
316
+ readonly ffmpeg: string;
317
+ readonly maxDecodedPcmBytes?: number;
318
+ readonly now: Date;
319
+ readonly project: OpenProject;
320
+ readonly repositoryRoot: string;
321
+ readonly runner: ProcessRunner;
322
+ readonly source: string;
323
+ readonly toolVersion: string;
324
+ }
325
+
326
+ export type AnalyzeAndPersistProjectMusicOptions = AnalyzeProjectMusicOptions;
327
+
328
+ export interface AnalyzedProjectMusic {
329
+ readonly analysis: MusicAnalysisV1;
330
+ }
331
+
332
+ export interface PersistedProjectMusicAnalysis {
333
+ readonly analysis: MusicAnalysisV1;
334
+ readonly analysisPath: string;
335
+ readonly project: VideoProjectV1;
336
+ readonly reference: MusicProjectAnalysisReference;
337
+ }
338
+
339
+ /** Resolve, decode, and analyze a complete immutable music subject without publishing project state. */
340
+ export async function analyzeProjectMusic(
341
+ options: AnalyzeProjectMusicOptions,
342
+ ): Promise<AnalyzedProjectMusic> {
343
+ const selected = resolveAudioAnalysisSubject(options.project.project, options.source);
344
+ const analysisId = AnalysisIdSchema.parse(
345
+ options.analysisId ?? `analysis_${randomUUID().replaceAll("-", "")}`,
346
+ );
347
+ if (options.project.project.analyses.some(existing => existing.analysisId === analysisId)) {
348
+ throw new CliError("conflict", `Project analysis already exists: ${analysisId}`);
349
+ }
350
+ const analysisPath = projectAnalysisPath("music", analysisId);
351
+ await requireUnusedAnalysisPath(options.project, analysisPath);
352
+
353
+ const config = validateMusicConfig(options.config ?? DEFAULT_MUSIC_ANALYSIS_CONFIG);
354
+ const tool = AnalysisToolSchema.parse({
355
+ name: "slopcamera-music-analyzer",
356
+ profile: "mono-pcm-spectral-v1",
357
+ version: options.toolVersion,
358
+ });
359
+ const createdAt = options.now.toISOString();
360
+ const inputDigest = canonicalJsonSha256({
361
+ config,
362
+ decoding: { channels: 1, format: "f32le", sampleRateHz: MUSIC_ANALYSIS_SAMPLE_RATE_HZ },
363
+ subject: selected.subject,
364
+ });
365
+ const analysis = await withDecodedMusicPcm({
366
+ ffmpeg: options.ffmpeg,
367
+ ...(options.maxDecodedPcmBytes === undefined
368
+ ? {}
369
+ : { maxDecodedPcmBytes: options.maxDecodedPcmBytes }),
370
+ projectDirectory: options.project.directory.path,
371
+ repositoryRoot: options.repositoryRoot,
372
+ runner: options.runner,
373
+ stream: selected.stream,
374
+ }, pcm => analyzeMusic({
375
+ analysisId,
376
+ config,
377
+ createdAt,
378
+ inputDigest,
379
+ pcm,
380
+ subject: selected.subject,
381
+ tool,
382
+ }));
383
+ assertCompleteMusicAnalysis(analysis, selected.stream);
384
+ return { analysis };
385
+ }
386
+
387
+ export interface PublishedProjectMusicAnalysisArtifact {
388
+ readonly analysisPath: string;
389
+ readonly reference: MusicProjectAnalysisReference;
390
+ }
391
+
392
+ export async function publishProjectMusicAnalysisArtifact(options: {
393
+ readonly analysis: MusicAnalysisV1;
394
+ readonly project: OpenProject;
395
+ }): Promise<PublishedProjectMusicAnalysisArtifact> {
396
+ const analysis = MusicAnalysisV1Schema.parse(options.analysis);
397
+ const analysisPath = projectAnalysisPath("music", analysis.analysisId);
398
+ await requireUnusedAnalysisPath(options.project, analysisPath);
399
+ const reference = buildMusicAnalysisReference(analysis, analysisPath);
400
+ await saveMusicAnalysisArtifactOnce(options.project, analysis, analysisPath);
401
+ const persistedText = await options.project.fileSystem.readText(analysisPath);
402
+ if (sha256Hex(persistedText) !== reference.sha256) {
403
+ throw new CliError(
404
+ "conflict",
405
+ "Persisted music-analysis sidecar failed its content-addressed verification.",
406
+ );
407
+ }
408
+ return { analysisPath, reference };
409
+ }
410
+
411
+ /** Resolve, decode, analyze, and persist a music sidecar plus its validated project reference. */
412
+ export async function analyzeAndPersistProjectMusic(
413
+ options: AnalyzeAndPersistProjectMusicOptions,
414
+ ): Promise<PersistedProjectMusicAnalysis> {
415
+ const analyzed = await analyzeProjectMusic(options);
416
+ const published = await publishProjectMusicAnalysisArtifact({
417
+ analysis: analyzed.analysis,
418
+ project: options.project,
419
+ });
420
+ const update = buildMusicProjectUpdate({
421
+ analysis: analyzed.analysis,
422
+ analysisPath: published.analysisPath,
423
+ project: options.project.project,
424
+ updatedAt: analyzed.analysis.createdAt,
425
+ });
426
+ const project = canonicalSlopcameraPersistenceDocument(update.project);
427
+ await saveVideoProject(options.project.fileSystem, project);
428
+ return {
429
+ analysis: analyzed.analysis,
430
+ analysisPath: published.analysisPath,
431
+ ...update,
432
+ project,
433
+ };
434
+ }