@crievo/omni 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (709) hide show
  1. package/.env.example +41 -0
  2. package/README.md +2 -2
  3. package/bin/omni.mjs +14 -0
  4. package/docs/onboarding/clone-voice.png +0 -0
  5. package/docs/onboarding/getting-started.png +0 -0
  6. package/docs/onboarding/new-video.png +0 -0
  7. package/docs/onboarding/providers.png +0 -0
  8. package/docs/onboarding/publish.png +0 -0
  9. package/docs/onboarding/references.png +0 -0
  10. package/docs/onboarding.md +212 -0
  11. package/omnivideo.config.example.json +437 -0
  12. package/package.json +40 -4
  13. package/reference/captions.md +156 -0
  14. package/reference/carousel-contract.md +91 -0
  15. package/reference/cloud.md +96 -0
  16. package/reference/learnings.md +792 -0
  17. package/reference/omnichat.md +305 -0
  18. package/reference/premiere-mcp.md +91 -0
  19. package/reference/publish.md +305 -0
  20. package/reference/scenes-contract.md +109 -0
  21. package/reference/studio.md +666 -0
  22. package/reference/voicestudio.md +81 -0
  23. package/reference/writing-for-voice.md +67 -0
  24. package/requirements.txt +3 -0
  25. package/studio/api/app.ts +87 -0
  26. package/studio/api/lib/errors.ts +33 -0
  27. package/studio/api/lib/files.ts +41 -0
  28. package/studio/api/lib/hono.ts +9 -0
  29. package/studio/api/lib/result.ts +14 -0
  30. package/studio/api/lib/validate.ts +31 -0
  31. package/studio/api/lib/video.ts +10 -0
  32. package/studio/api/middleware/json-charset.ts +11 -0
  33. package/studio/api/middleware/local-only.ts +32 -0
  34. package/studio/api/middleware/no-timeout.ts +10 -0
  35. package/studio/api/routes/admin.ts +18 -0
  36. package/studio/api/routes/agent-cascade.ts +100 -0
  37. package/studio/api/routes/agent-history.ts +23 -0
  38. package/studio/api/routes/agent.ts +162 -0
  39. package/studio/api/routes/analytics.ts +31 -0
  40. package/studio/api/routes/approval.ts +60 -0
  41. package/studio/api/routes/archive.ts +12 -0
  42. package/studio/api/routes/captions.ts +28 -0
  43. package/studio/api/routes/carousel.ts +38 -0
  44. package/studio/api/routes/clone.ts +27 -0
  45. package/studio/api/routes/create.ts +19 -0
  46. package/studio/api/routes/events.ts +11 -0
  47. package/studio/api/routes/export.ts +14 -0
  48. package/studio/api/routes/media.ts +30 -0
  49. package/studio/api/routes/omnichat.ts +113 -0
  50. package/studio/api/routes/onboarding.ts +14 -0
  51. package/studio/api/routes/publish.ts +40 -0
  52. package/studio/api/routes/queue.ts +14 -0
  53. package/studio/api/routes/references.ts +40 -0
  54. package/studio/api/routes/runner.ts +17 -0
  55. package/studio/api/routes/schedule.ts +47 -0
  56. package/studio/api/routes/services.ts +11 -0
  57. package/studio/api/routes/sessions.ts +27 -0
  58. package/studio/api/routes/settings.ts +102 -0
  59. package/studio/api/routes/sound.ts +45 -0
  60. package/studio/api/routes/thumbnail.ts +18 -0
  61. package/studio/api/routes/usage.ts +13 -0
  62. package/studio/api/routes/videos.ts +41 -0
  63. package/studio/api/routes/voices.ts +71 -0
  64. package/studio/api/services/admin.ts +113 -0
  65. package/studio/api/services/agent-cascade.ts +975 -0
  66. package/studio/api/services/agent-claude.ts +47 -0
  67. package/studio/api/services/agent-history.ts +103 -0
  68. package/studio/api/services/agent-persona.ts +77 -0
  69. package/studio/api/services/agent-tools.ts +2237 -0
  70. package/studio/api/services/agent-ui-tools.ts +259 -0
  71. package/studio/api/services/agent-wait.ts +32 -0
  72. package/studio/api/services/agent.ts +553 -0
  73. package/studio/api/services/analytics.ts +92 -0
  74. package/studio/api/services/approval.ts +134 -0
  75. package/studio/api/services/archive.ts +38 -0
  76. package/studio/api/services/captions.ts +268 -0
  77. package/studio/api/services/carousel.ts +206 -0
  78. package/studio/api/services/classify.ts +196 -0
  79. package/studio/api/services/clone-phrases.ts +75 -0
  80. package/studio/api/services/clone.ts +722 -0
  81. package/studio/api/services/create.ts +421 -0
  82. package/studio/api/services/events.ts +215 -0
  83. package/studio/api/services/media.ts +32 -0
  84. package/studio/api/services/omnichat.ts +624 -0
  85. package/studio/api/services/onboarding.ts +77 -0
  86. package/studio/api/services/publish.ts +287 -0
  87. package/studio/api/services/queue.ts +150 -0
  88. package/studio/api/services/references.ts +269 -0
  89. package/studio/api/services/root.ts +60 -0
  90. package/studio/api/services/services.ts +114 -0
  91. package/studio/api/services/sessions.ts +1007 -0
  92. package/studio/api/services/settings.ts +983 -0
  93. package/studio/api/services/sound.ts +311 -0
  94. package/studio/api/services/thumbnail.ts +133 -0
  95. package/studio/api/services/usage.ts +27 -0
  96. package/studio/api/services/videos.ts +418 -0
  97. package/studio/api/services/voice-pause.ts +98 -0
  98. package/studio/api/services/voices.ts +324 -0
  99. package/studio/runner/runner.ts +244 -0
  100. package/studio/shared/omni-actions.ts +68 -0
  101. package/studio/shared/omni-admin.ts +52 -0
  102. package/studio/shared/omni-farewell.ts +48 -0
  103. package/studio/shared/omni-hotword.ts +20 -0
  104. package/studio/shared/omni-screens.ts +228 -0
  105. package/studio/shared/schemas/admin.ts +15 -0
  106. package/studio/shared/schemas/agent-cascade.ts +58 -0
  107. package/studio/shared/schemas/agent-history.ts +73 -0
  108. package/studio/shared/schemas/agent.ts +50 -0
  109. package/studio/shared/schemas/analytics.ts +6 -0
  110. package/studio/shared/schemas/approval.ts +22 -0
  111. package/studio/shared/schemas/captions.ts +19 -0
  112. package/studio/shared/schemas/carousel.ts +26 -0
  113. package/studio/shared/schemas/clone.ts +40 -0
  114. package/studio/shared/schemas/common.ts +16 -0
  115. package/studio/shared/schemas/omnichat.ts +54 -0
  116. package/studio/shared/schemas/onboarding.ts +5 -0
  117. package/studio/shared/schemas/publish.ts +45 -0
  118. package/studio/shared/schemas/references.ts +20 -0
  119. package/studio/shared/schemas/settings.ts +82 -0
  120. package/studio/shared/schemas/sound.ts +22 -0
  121. package/studio/shared/schemas/videos.ts +38 -0
  122. package/studio/shared/schemas/voices.ts +23 -0
  123. package/studio/shared/types/api.ts +1290 -0
  124. package/studio/studio.ts +148 -0
  125. package/studio/tsconfig.json +15 -0
  126. package/studio/web/assets/logos/cartesia.svg +4 -0
  127. package/studio/web/assets/logos/elevenlabs.svg +12 -0
  128. package/studio/web/assets/logos/fish.svg +24 -0
  129. package/studio/web/assets/logos/omni-black.png +0 -0
  130. package/studio/web/assets/logos/omni-white.png +0 -0
  131. package/studio/web/assets/logos/voicestudio.svg +21 -0
  132. package/studio/web/components/AgentMark.tsx +7 -0
  133. package/studio/web/components/AgentWalking.tsx +11 -0
  134. package/studio/web/components/ApiKeyField.tsx +104 -0
  135. package/studio/web/components/AppShell.tsx +64 -0
  136. package/studio/web/components/ApprovalNotice.tsx +65 -0
  137. package/studio/web/components/AvatarGroupIcon.tsx +22 -0
  138. package/studio/web/components/AvatarNameField.tsx +86 -0
  139. package/studio/web/components/AvatarPicker.tsx +84 -0
  140. package/studio/web/components/BrandLogo.tsx +16 -0
  141. package/studio/web/components/CartesiaPlanSetting.tsx +90 -0
  142. package/studio/web/components/ClaudeWalking.tsx +32 -0
  143. package/studio/web/components/CloneVoiceDialog/GuidedScript.tsx +39 -0
  144. package/studio/web/components/CloneVoiceDialog/LanguageBar.tsx +37 -0
  145. package/studio/web/components/CloneVoiceDialog/LevelMeter.tsx +31 -0
  146. package/studio/web/components/CloneVoiceDialog/ProviderChoice.tsx +81 -0
  147. package/studio/web/components/CloneVoiceDialog/QualityPanel.tsx +62 -0
  148. package/studio/web/components/CloneVoiceDialog/RecordStep.tsx +50 -0
  149. package/studio/web/components/CloneVoiceDialog/RecorderBar.tsx +79 -0
  150. package/studio/web/components/CloneVoiceDialog/RedoSummary.tsx +54 -0
  151. package/studio/web/components/CloneVoiceDialog/SaveStep.tsx +91 -0
  152. package/studio/web/components/CloneVoiceDialog/SourceBar.tsx +30 -0
  153. package/studio/web/components/CloneVoiceDialog/index.tsx +141 -0
  154. package/studio/web/components/CloneVoiceDialog/types.ts +28 -0
  155. package/studio/web/components/CloneVoiceDialog/useCloneVoice.ts +528 -0
  156. package/studio/web/components/ConnectionSignals.tsx +51 -0
  157. package/studio/web/components/EditVoiceDialog/EditVoiceFooter.tsx +72 -0
  158. package/studio/web/components/EditVoiceDialog/ProviderCards.tsx +81 -0
  159. package/studio/web/components/EditVoiceDialog/index.tsx +256 -0
  160. package/studio/web/components/ErrorBanner.tsx +115 -0
  161. package/studio/web/components/FinishedTask.tsx +41 -0
  162. package/studio/web/components/FishModelSetting.tsx +69 -0
  163. package/studio/web/components/FishPriceSetting.tsx +44 -0
  164. package/studio/web/components/HumanBanner.tsx +64 -0
  165. package/studio/web/components/Lightbox.tsx +41 -0
  166. package/studio/web/components/LogStream.tsx +42 -0
  167. package/studio/web/components/Markdown.tsx +21 -0
  168. package/studio/web/components/MissingDmsPanel/BatchSend.tsx +137 -0
  169. package/studio/web/components/MissingDmsPanel/CommentCard.tsx +168 -0
  170. package/studio/web/components/MissingDmsPanel/CommentFeed.tsx +45 -0
  171. package/studio/web/components/MissingDmsPanel/DmBubble.tsx +38 -0
  172. package/studio/web/components/MissingDmsPanel/ReplyComposer.tsx +63 -0
  173. package/studio/web/components/MissingDmsPanel/index.tsx +98 -0
  174. package/studio/web/components/NotFound.tsx +48 -0
  175. package/studio/web/components/Omni/OmniAvatarPicker.tsx +92 -0
  176. package/studio/web/components/Omni/OmniCards.tsx +202 -0
  177. package/studio/web/components/Omni/OmniGlow.tsx +183 -0
  178. package/studio/web/components/Omni/OmniHistory.tsx +119 -0
  179. package/studio/web/components/Omni/OmniHotword.tsx +40 -0
  180. package/studio/web/components/Omni/OmniMicSelect.tsx +92 -0
  181. package/studio/web/components/Omni/OmniVoiceSample.tsx +81 -0
  182. package/studio/web/components/Omni/OmniWidget.tsx +743 -0
  183. package/studio/web/components/Omni/PendingActions.tsx +200 -0
  184. package/studio/web/components/Omni/Transcript.tsx +127 -0
  185. package/studio/web/components/OmnichatAccountStatus.tsx +87 -0
  186. package/studio/web/components/OmnichatRulePanel/RuleConfig.tsx +150 -0
  187. package/studio/web/components/OmnichatRulePanel/RuleDryRun.tsx +15 -0
  188. package/studio/web/components/OmnichatRulePanel/RuleEnableConfirm.tsx +42 -0
  189. package/studio/web/components/OmnichatRulePanel/RuleSkeleton.tsx +18 -0
  190. package/studio/web/components/OmnichatRulePanel/RuleStatus.tsx +55 -0
  191. package/studio/web/components/OmnichatRulePanel/index.tsx +115 -0
  192. package/studio/web/components/OmnichatRulePanel/rule-steps.ts +67 -0
  193. package/studio/web/components/OmnichatRulePanel/steps/MessageStep.tsx +91 -0
  194. package/studio/web/components/OmnichatRulePanel/steps/RepliesStep.tsx +201 -0
  195. package/studio/web/components/OmnichatRulePanel/steps/TriggerStep.tsx +76 -0
  196. package/studio/web/components/OmnichatRulePanel/steps/types.ts +11 -0
  197. package/studio/web/components/Palette.tsx +17 -0
  198. package/studio/web/components/PlatformCard.tsx +109 -0
  199. package/studio/web/components/PlatformIcon.tsx +19 -0
  200. package/studio/web/components/ProviderCard.tsx +80 -0
  201. package/studio/web/components/ProviderDetail.tsx +367 -0
  202. package/studio/web/components/ProviderLogo.tsx +20 -0
  203. package/studio/web/components/QueuePanel.tsx +77 -0
  204. package/studio/web/components/ReferenceActions.tsx +89 -0
  205. package/studio/web/components/ReferencePicker.tsx +136 -0
  206. package/studio/web/components/ResultMessage.tsx +7 -0
  207. package/studio/web/components/RuleForm.tsx +138 -0
  208. package/studio/web/components/RuleLog.tsx +151 -0
  209. package/studio/web/components/RunningTask.tsx +23 -0
  210. package/studio/web/components/SampleList.tsx +55 -0
  211. package/studio/web/components/SessionPanel.tsx +352 -0
  212. package/studio/web/components/SettingRow.tsx +14 -0
  213. package/studio/web/components/SettingsCategorySkeleton.tsx +25 -0
  214. package/studio/web/components/SidePanel.tsx +77 -0
  215. package/studio/web/components/Sidebar.tsx +289 -0
  216. package/studio/web/components/StageStepper.tsx +87 -0
  217. package/studio/web/components/StudioOffline.tsx +80 -0
  218. package/studio/web/components/TaskName.tsx +17 -0
  219. package/studio/web/components/TopBanners.tsx +30 -0
  220. package/studio/web/components/VideoOmnichatPanel.tsx +287 -0
  221. package/studio/web/components/VoiceAvatar.tsx +33 -0
  222. package/studio/web/components/VoiceCarousel.tsx +196 -0
  223. package/studio/web/components/VoiceCategory.tsx +9 -0
  224. package/studio/web/components/VoiceModal.tsx +39 -0
  225. package/studio/web/components/VoiceProfileCard.tsx +75 -0
  226. package/studio/web/components/VoiceProfilePicker.tsx +107 -0
  227. package/studio/web/components/VoiceProfilesSettings.tsx +150 -0
  228. package/studio/web/components/VoiceProviderCard.tsx +118 -0
  229. package/studio/web/components/VoiceProvidersCard.tsx +26 -0
  230. package/studio/web/components/WaitList.tsx +21 -0
  231. package/studio/web/components/icons.tsx +247 -0
  232. package/studio/web/components/ui/accordion.tsx +47 -0
  233. package/studio/web/components/ui/badge.tsx +18 -0
  234. package/studio/web/components/ui/calendar.tsx +43 -0
  235. package/studio/web/components/ui/chart.tsx +229 -0
  236. package/studio/web/components/ui/checkbox.tsx +26 -0
  237. package/studio/web/components/ui/cx.ts +2 -0
  238. package/studio/web/components/ui/date-time-picker.tsx +84 -0
  239. package/studio/web/components/ui/panel.tsx +23 -0
  240. package/studio/web/components/ui/picker.tsx +58 -0
  241. package/studio/web/components/ui/popover.tsx +24 -0
  242. package/studio/web/components/ui/select.tsx +89 -0
  243. package/studio/web/components/ui/skeleton.tsx +10 -0
  244. package/studio/web/components/ui/slider.tsx +68 -0
  245. package/studio/web/components/ui/switch.tsx +13 -0
  246. package/studio/web/components/ui/tooltip.test.tsx +30 -0
  247. package/studio/web/components/ui/tooltip.tsx +59 -0
  248. package/studio/web/hooks/useAccounts.ts +27 -0
  249. package/studio/web/hooks/useAdmin.ts +12 -0
  250. package/studio/web/hooks/useAnalytics.ts +21 -0
  251. package/studio/web/hooks/useAvatarAudioReactive.ts +87 -0
  252. package/studio/web/hooks/useBrowserConnect.ts +48 -0
  253. package/studio/web/hooks/useClonePlan.ts +14 -0
  254. package/studio/web/hooks/useConnected.ts +7 -0
  255. package/studio/web/hooks/useLiveQuery.ts +29 -0
  256. package/studio/web/hooks/useMenuCollapsed.ts +32 -0
  257. package/studio/web/hooks/useMicDevices.ts +53 -0
  258. package/studio/web/hooks/useMissingDms.ts +211 -0
  259. package/studio/web/hooks/useMissingSetupSteps.ts +26 -0
  260. package/studio/web/hooks/useNow.ts +11 -0
  261. package/studio/web/hooks/useOmniAgent.ts +278 -0
  262. package/studio/web/hooks/useOmniClaude.ts +124 -0
  263. package/studio/web/hooks/useOmniDock.ts +142 -0
  264. package/studio/web/hooks/useOmniGreeting.ts +60 -0
  265. package/studio/web/hooks/useOmniHotword.ts +128 -0
  266. package/studio/web/hooks/useOmnichat.ts +26 -0
  267. package/studio/web/hooks/useOnboarding.ts +27 -0
  268. package/studio/web/hooks/usePost.ts +23 -0
  269. package/studio/web/hooks/useQueue.ts +10 -0
  270. package/studio/web/hooks/useReferences.ts +20 -0
  271. package/studio/web/hooks/useRuleEditor.ts +101 -0
  272. package/studio/web/hooks/useRunner.ts +24 -0
  273. package/studio/web/hooks/useSaveAction.ts +23 -0
  274. package/studio/web/hooks/useSchedule.ts +11 -0
  275. package/studio/web/hooks/useServices.ts +10 -0
  276. package/studio/web/hooks/useSessions.ts +15 -0
  277. package/studio/web/hooks/useSettings.ts +11 -0
  278. package/studio/web/hooks/useSettingsSection.ts +30 -0
  279. package/studio/web/hooks/useSidePanel.ts +23 -0
  280. package/studio/web/hooks/useSound.ts +30 -0
  281. package/studio/web/hooks/useUsage.ts +11 -0
  282. package/studio/web/hooks/useVideoVoice.ts +36 -0
  283. package/studio/web/hooks/useVideos.ts +21 -0
  284. package/studio/web/hooks/useVoiceProfilePreview.ts +52 -0
  285. package/studio/web/hooks/useVoiceProfiles.ts +7 -0
  286. package/studio/web/hooks/useVoiceRunning.ts +10 -0
  287. package/studio/web/hooks/useVoiceStudioClosed.ts +8 -0
  288. package/studio/web/index.html +19 -0
  289. package/studio/web/lib/agent.ts +7 -0
  290. package/studio/web/lib/alerts.ts +210 -0
  291. package/studio/web/lib/analytics-metrics.ts +351 -0
  292. package/studio/web/lib/api.ts +70 -0
  293. package/studio/web/lib/audio.ts +39 -0
  294. package/studio/web/lib/avatars.ts +41 -0
  295. package/studio/web/lib/calendar.ts +218 -0
  296. package/studio/web/lib/caption-position.ts +249 -0
  297. package/studio/web/lib/carousel.ts +24 -0
  298. package/studio/web/lib/chime.ts +42 -0
  299. package/studio/web/lib/clone-voice.ts +55 -0
  300. package/studio/web/lib/export.ts +11 -0
  301. package/studio/web/lib/files.ts +8 -0
  302. package/studio/web/lib/format.ts +93 -0
  303. package/studio/web/lib/media.ts +29 -0
  304. package/studio/web/lib/mic-device.ts +72 -0
  305. package/studio/web/lib/missing-dms.ts +70 -0
  306. package/studio/web/lib/omni-cards.ts +42 -0
  307. package/studio/web/lib/omni-cascade.ts +1067 -0
  308. package/studio/web/lib/omni-greeting-audio.ts +24 -0
  309. package/studio/web/lib/omni-hotword.ts +231 -0
  310. package/studio/web/lib/omni-panel.ts +67 -0
  311. package/studio/web/lib/omni-realtime.ts +811 -0
  312. package/studio/web/lib/omni-ui.ts +402 -0
  313. package/studio/web/lib/omni-wait.ts +174 -0
  314. package/studio/web/lib/omnichat-api.ts +44 -0
  315. package/studio/web/lib/orb.ts +270 -0
  316. package/studio/web/lib/pcm-recorder.ts +115 -0
  317. package/studio/web/lib/platforms.ts +17 -0
  318. package/studio/web/lib/providers.ts +27 -0
  319. package/studio/web/lib/publish-draft.ts +52 -0
  320. package/studio/web/lib/publish-settings.ts +81 -0
  321. package/studio/web/lib/query-client.ts +122 -0
  322. package/studio/web/lib/references.ts +13 -0
  323. package/studio/web/lib/services.ts +40 -0
  324. package/studio/web/lib/sessions.ts +103 -0
  325. package/studio/web/lib/settings-categories.ts +58 -0
  326. package/studio/web/lib/sound-audio.ts +33 -0
  327. package/studio/web/lib/sound-timeline.ts +291 -0
  328. package/studio/web/lib/sse.ts +86 -0
  329. package/studio/web/lib/stage-history.ts +141 -0
  330. package/studio/web/lib/stages.ts +344 -0
  331. package/studio/web/lib/studio-offline.ts +60 -0
  332. package/studio/web/lib/upload.ts +24 -0
  333. package/studio/web/lib/video-tabs.ts +118 -0
  334. package/studio/web/lib/voice-profile-draft.ts +34 -0
  335. package/studio/web/lib/voice-profiles.ts +3 -0
  336. package/studio/web/main.tsx +14 -0
  337. package/studio/web/pages/AdminPage/index.tsx +123 -0
  338. package/studio/web/pages/AgentPage/components/Measurements.tsx +102 -0
  339. package/studio/web/pages/AgentPage/index.tsx +28 -0
  340. package/studio/web/pages/AnalyticsPage/components/About.tsx +52 -0
  341. package/studio/web/pages/AnalyticsPage/components/ChannelCards.tsx +106 -0
  342. package/studio/web/pages/AnalyticsPage/components/ChannelView.tsx +137 -0
  343. package/studio/web/pages/AnalyticsPage/components/CollectionStatus.tsx +46 -0
  344. package/studio/web/pages/AnalyticsPage/components/Comparison.tsx +68 -0
  345. package/studio/web/pages/AnalyticsPage/components/Diagnosis.tsx +50 -0
  346. package/studio/web/pages/AnalyticsPage/components/Highlights.tsx +49 -0
  347. package/studio/web/pages/AnalyticsPage/components/Hint.tsx +11 -0
  348. package/studio/web/pages/AnalyticsPage/components/HookVsBody.tsx +100 -0
  349. package/studio/web/pages/AnalyticsPage/components/HourlyWave.tsx +133 -0
  350. package/studio/web/pages/AnalyticsPage/components/Insights.tsx +70 -0
  351. package/studio/web/pages/AnalyticsPage/components/MetricCard.tsx +46 -0
  352. package/studio/web/pages/AnalyticsPage/components/PlanSection.tsx +40 -0
  353. package/studio/web/pages/AnalyticsPage/components/ProgressionChart.tsx +155 -0
  354. package/studio/web/pages/AnalyticsPage/components/ReelBarsChart.tsx +154 -0
  355. package/studio/web/pages/AnalyticsPage/components/ReelThumb.tsx +19 -0
  356. package/studio/web/pages/AnalyticsPage/components/ReelView.tsx +290 -0
  357. package/studio/web/pages/AnalyticsPage/components/ReelsTable.tsx +269 -0
  358. package/studio/web/pages/AnalyticsPage/components/RequestWithFocus.tsx +38 -0
  359. package/studio/web/pages/AnalyticsPage/components/SegmentedControl.tsx +35 -0
  360. package/studio/web/pages/AnalyticsPage/components/VsMedian.tsx +31 -0
  361. package/studio/web/pages/AnalyticsPage/index.tsx +17 -0
  362. package/studio/web/pages/GettingStartedPage/index.tsx +174 -0
  363. package/studio/web/pages/NewVideoPage/components/DeliverablesPicker.tsx +62 -0
  364. package/studio/web/pages/NewVideoPage/components/FormatPicker.tsx +33 -0
  365. package/studio/web/pages/NewVideoPage/index.tsx +133 -0
  366. package/studio/web/pages/NotFoundPage/index.tsx +9 -0
  367. package/studio/web/pages/OmnichatPage/components/NewFromLink.tsx +59 -0
  368. package/studio/web/pages/OmnichatPage/components/RuleListItem.tsx +64 -0
  369. package/studio/web/pages/OmnichatPage/components/RulePreview.tsx +14 -0
  370. package/studio/web/pages/OmnichatPage/components/RuleView.tsx +88 -0
  371. package/studio/web/pages/OmnichatPage/index.tsx +153 -0
  372. package/studio/web/pages/QueuePage/index.tsx +22 -0
  373. package/studio/web/pages/ReferencePage/components/ReferenceTexts.tsx +131 -0
  374. package/studio/web/pages/ReferencePage/index.tsx +216 -0
  375. package/studio/web/pages/ReferencesPage/components/NewReferenceForm.tsx +150 -0
  376. package/studio/web/pages/ReferencesPage/components/ReferenceCard.tsx +42 -0
  377. package/studio/web/pages/ReferencesPage/components/ReferenceGridSkeleton.tsx +31 -0
  378. package/studio/web/pages/ReferencesPage/index.tsx +67 -0
  379. package/studio/web/pages/SchedulePage/components/CalendarSkeleton.tsx +18 -0
  380. package/studio/web/pages/SchedulePage/components/DayView.tsx +177 -0
  381. package/studio/web/pages/SchedulePage/components/HourPicker.tsx +103 -0
  382. package/studio/web/pages/SchedulePage/components/MonthDayCell.tsx +111 -0
  383. package/studio/web/pages/SchedulePage/components/MonthView.tsx +47 -0
  384. package/studio/web/pages/SchedulePage/components/PlatformIcons.tsx +12 -0
  385. package/studio/web/pages/SchedulePage/components/PostThumb.tsx +15 -0
  386. package/studio/web/pages/SchedulePage/components/WeekColumn.tsx +75 -0
  387. package/studio/web/pages/SchedulePage/components/WeekView.tsx +35 -0
  388. package/studio/web/pages/SchedulePage/index.tsx +285 -0
  389. package/studio/web/pages/SettingsPage/components/AgentAccounts.tsx +263 -0
  390. package/studio/web/pages/SettingsPage/components/AgentCategory.tsx +20 -0
  391. package/studio/web/pages/SettingsPage/components/ModelPicker.tsx +49 -0
  392. package/studio/web/pages/SettingsPage/components/ModelsSettings.tsx +138 -0
  393. package/studio/web/pages/SettingsPage/components/PlatformsCategory.tsx +23 -0
  394. package/studio/web/pages/SettingsPage/components/PublishingCategory/AccountStatus.tsx +29 -0
  395. package/studio/web/pages/SettingsPage/components/PublishingCategory/AutoPublishPanel.tsx +90 -0
  396. package/studio/web/pages/SettingsPage/components/PublishingCategory/BrowserConnect.tsx +62 -0
  397. package/studio/web/pages/SettingsPage/components/PublishingCategory/CheckField.tsx +19 -0
  398. package/studio/web/pages/SettingsPage/components/PublishingCategory/InstagramPanel.tsx +113 -0
  399. package/studio/web/pages/SettingsPage/components/PublishingCategory/OmnichatSettingsPanel.tsx +146 -0
  400. package/studio/web/pages/SettingsPage/components/PublishingCategory/SaveBar.tsx +33 -0
  401. package/studio/web/pages/SettingsPage/components/PublishingCategory/SchedulePanel.tsx +132 -0
  402. package/studio/web/pages/SettingsPage/components/PublishingCategory/SettingsCard.tsx +47 -0
  403. package/studio/web/pages/SettingsPage/components/PublishingCategory/SettingsCardGroup.tsx +19 -0
  404. package/studio/web/pages/SettingsPage/components/PublishingCategory/TextPanel.tsx +55 -0
  405. package/studio/web/pages/SettingsPage/components/PublishingCategory/TiktokPanel.tsx +123 -0
  406. package/studio/web/pages/SettingsPage/components/PublishingCategory/YoutubePanel.tsx +100 -0
  407. package/studio/web/pages/SettingsPage/components/PublishingCategory/badges.tsx +15 -0
  408. package/studio/web/pages/SettingsPage/components/PublishingCategory/index.tsx +40 -0
  409. package/studio/web/pages/SettingsPage/components/PublishingCategory/types.ts +10 -0
  410. package/studio/web/pages/SettingsPage/components/UsageCategory.tsx +389 -0
  411. package/studio/web/pages/SettingsPage/components/VoiceProvidersCategory.tsx +97 -0
  412. package/studio/web/pages/SettingsPage/index.tsx +85 -0
  413. package/studio/web/pages/VideoPage/components/AudioPlayer.tsx +113 -0
  414. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/AxisField.tsx +50 -0
  415. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionList.tsx +56 -0
  416. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionListItem.tsx +92 -0
  417. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionMargins.tsx +57 -0
  418. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionStyleFields.tsx +70 -0
  419. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionTextForm.tsx +87 -0
  420. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/ColorField.tsx +37 -0
  421. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/MarginField.tsx +59 -0
  422. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/NumberField.tsx +57 -0
  423. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/PositionControls.tsx +140 -0
  424. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/captionEditor.ts +215 -0
  425. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/useCaptionPosition.ts +154 -0
  426. package/studio/web/pages/VideoPage/components/CarouselChangeRequest.tsx +72 -0
  427. package/studio/web/pages/VideoPage/components/CarouselCompositionStage.tsx +80 -0
  428. package/studio/web/pages/VideoPage/components/CarouselCreate.tsx +63 -0
  429. package/studio/web/pages/VideoPage/components/CarouselMissing.tsx +19 -0
  430. package/studio/web/pages/VideoPage/components/CarouselPublishStage.tsx +173 -0
  431. package/studio/web/pages/VideoPage/components/CarouselScriptStage.tsx +82 -0
  432. package/studio/web/pages/VideoPage/components/CarouselSlideCard.tsx +49 -0
  433. package/studio/web/pages/VideoPage/components/CarouselViewer.tsx +55 -0
  434. package/studio/web/pages/VideoPage/components/ChangedVoices.tsx +53 -0
  435. package/studio/web/pages/VideoPage/components/CompositionStage.tsx +78 -0
  436. package/studio/web/pages/VideoPage/components/CoverPicker.tsx +32 -0
  437. package/studio/web/pages/VideoPage/components/EditorStage.tsx +53 -0
  438. package/studio/web/pages/VideoPage/components/LineTakes.tsx +160 -0
  439. package/studio/web/pages/VideoPage/components/LineTextEditor.tsx +124 -0
  440. package/studio/web/pages/VideoPage/components/LineVoicePicker.tsx +37 -0
  441. package/studio/web/pages/VideoPage/components/MediaImage.tsx +25 -0
  442. package/studio/web/pages/VideoPage/components/MotionScriptStage.tsx +39 -0
  443. package/studio/web/pages/VideoPage/components/NarrationStage.tsx +70 -0
  444. package/studio/web/pages/VideoPage/components/PublishForm.tsx +387 -0
  445. package/studio/web/pages/VideoPage/components/PublishStage.tsx +76 -0
  446. package/studio/web/pages/VideoPage/components/PublishText.tsx +149 -0
  447. package/studio/web/pages/VideoPage/components/ScenesApprovalPanel.tsx +278 -0
  448. package/studio/web/pages/VideoPage/components/ScheduledInfo.tsx +72 -0
  449. package/studio/web/pages/VideoPage/components/ScriptApprovalPanel.tsx +189 -0
  450. package/studio/web/pages/VideoPage/components/ScriptStage.tsx +17 -0
  451. package/studio/web/pages/VideoPage/components/SequencePlayer.tsx +146 -0
  452. package/studio/web/pages/VideoPage/components/SoundTimeline/AddSound.tsx +117 -0
  453. package/studio/web/pages/VideoPage/components/SoundTimeline/AreaSelection.tsx +74 -0
  454. package/studio/web/pages/VideoPage/components/SoundTimeline/BedPanel.tsx +94 -0
  455. package/studio/web/pages/VideoPage/components/SoundTimeline/BedRow.tsx +45 -0
  456. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionOverlay.tsx +44 -0
  457. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionSaveBar.tsx +32 -0
  458. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionsPanel.tsx +22 -0
  459. package/studio/web/pages/VideoPage/components/SoundTimeline/ClipBlock.tsx +111 -0
  460. package/studio/web/pages/VideoPage/components/SoundTimeline/ClipPanel.tsx +76 -0
  461. package/studio/web/pages/VideoPage/components/SoundTimeline/CutsPanel.tsx +44 -0
  462. package/studio/web/pages/VideoPage/components/SoundTimeline/EditorTabBar.tsx +73 -0
  463. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectChoice.tsx +46 -0
  464. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectItem.tsx +89 -0
  465. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectPanel.tsx +232 -0
  466. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectsRow.tsx +21 -0
  467. package/studio/web/pages/VideoPage/components/SoundTimeline/Lane.tsx +39 -0
  468. package/studio/web/pages/VideoPage/components/SoundTimeline/MarkerRow.tsx +40 -0
  469. package/studio/web/pages/VideoPage/components/SoundTimeline/MuteButton.tsx +36 -0
  470. package/studio/web/pages/VideoPage/components/SoundTimeline/NumberInput.tsx +37 -0
  471. package/studio/web/pages/VideoPage/components/SoundTimeline/PlanWarnings.tsx +19 -0
  472. package/studio/web/pages/VideoPage/components/SoundTimeline/PlayerResizeHandle.tsx +50 -0
  473. package/studio/web/pages/VideoPage/components/SoundTimeline/PreviewControls.tsx +51 -0
  474. package/studio/web/pages/VideoPage/components/SoundTimeline/SceneRow.tsx +54 -0
  475. package/studio/web/pages/VideoPage/components/SoundTimeline/SettingsPanel.tsx +65 -0
  476. package/studio/web/pages/VideoPage/components/SoundTimeline/SoundLibrary.tsx +99 -0
  477. package/studio/web/pages/VideoPage/components/SoundTimeline/SoundPreview.tsx +82 -0
  478. package/studio/web/pages/VideoPage/components/SoundTimeline/SpeedPanel.tsx +128 -0
  479. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineDock.tsx +56 -0
  480. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineFooter.tsx +26 -0
  481. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineSkeleton.tsx +21 -0
  482. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineToolbar.tsx +160 -0
  483. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineTracks.tsx +121 -0
  484. package/studio/web/pages/VideoPage/components/SoundTimeline/TrackSidebar.tsx +30 -0
  485. package/studio/web/pages/VideoPage/components/SoundTimeline/VoiceRow.tsx +53 -0
  486. package/studio/web/pages/VideoPage/components/SoundTimeline/Waveform.tsx +97 -0
  487. package/studio/web/pages/VideoPage/components/SoundTimeline/clipActions.ts +50 -0
  488. package/studio/web/pages/VideoPage/components/SoundTimeline/effectActions.ts +161 -0
  489. package/studio/web/pages/VideoPage/components/SoundTimeline/index.tsx +161 -0
  490. package/studio/web/pages/VideoPage/components/SoundTimeline/timelineGeometry.ts +45 -0
  491. package/studio/web/pages/VideoPage/components/SoundTimeline/types.ts +43 -0
  492. package/studio/web/pages/VideoPage/components/SoundTimeline/useAutosave.ts +107 -0
  493. package/studio/web/pages/VideoPage/components/SoundTimeline/useEditorShortcuts.ts +168 -0
  494. package/studio/web/pages/VideoPage/components/SoundTimeline/useFitHeight.ts +40 -0
  495. package/studio/web/pages/VideoPage/components/SoundTimeline/useFollowPlayhead.ts +21 -0
  496. package/studio/web/pages/VideoPage/components/SoundTimeline/useListenMix.ts +43 -0
  497. package/studio/web/pages/VideoPage/components/SoundTimeline/usePlayback.ts +231 -0
  498. package/studio/web/pages/VideoPage/components/SoundTimeline/usePlayerWidth.ts +27 -0
  499. package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundData.ts +124 -0
  500. package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundTimeline.ts +511 -0
  501. package/studio/web/pages/VideoPage/components/StageDrawer/CompareVersions.tsx +111 -0
  502. package/studio/web/pages/VideoPage/components/StageDrawer/VersionCard.tsx +81 -0
  503. package/studio/web/pages/VideoPage/components/StageDrawer/VersionMedia.tsx +24 -0
  504. package/studio/web/pages/VideoPage/components/StageDrawer/VersionText.tsx +15 -0
  505. package/studio/web/pages/VideoPage/components/StageDrawer/index.tsx +109 -0
  506. package/studio/web/pages/VideoPage/components/StageProcess.tsx +126 -0
  507. package/studio/web/pages/VideoPage/components/StageResult.tsx +69 -0
  508. package/studio/web/pages/VideoPage/components/SwapReference.tsx +92 -0
  509. package/studio/web/pages/VideoPage/components/ThumbnailPanel.tsx +252 -0
  510. package/studio/web/pages/VideoPage/components/TimingPanel.tsx +89 -0
  511. package/studio/web/pages/VideoPage/components/VideoPlayer.tsx +18 -0
  512. package/studio/web/pages/VideoPage/components/VideoSkeleton.tsx +50 -0
  513. package/studio/web/pages/VideoPage/components/VideoVoiceInfo.tsx +33 -0
  514. package/studio/web/pages/VideoPage/components/VideoVoicePicker.tsx +165 -0
  515. package/studio/web/pages/VideoPage/components/VoicePauseBanner.tsx +56 -0
  516. package/studio/web/pages/VideoPage/components/WithLogo.tsx +10 -0
  517. package/studio/web/pages/VideoPage/index.tsx +295 -0
  518. package/studio/web/pages/VideosPage/components/PublishedOn.tsx +85 -0
  519. package/studio/web/pages/VideosPage/components/ServicesPanel.tsx +81 -0
  520. package/studio/web/pages/VideosPage/components/SessionsPanel.tsx +62 -0
  521. package/studio/web/pages/VideosPage/components/VideoListSkeleton.tsx +25 -0
  522. package/studio/web/pages/VideosPage/components/VideoRow.tsx +122 -0
  523. package/studio/web/pages/VideosPage/index.tsx +134 -0
  524. package/studio/web/pages/VoicePage/index.tsx +28 -0
  525. package/studio/web/router.tsx +201 -0
  526. package/studio/web/styles/base/elements.css +55 -0
  527. package/studio/web/styles/base/utilities.css +25 -0
  528. package/studio/web/styles/components/badge.css +33 -0
  529. package/studio/web/styles/components/claude-running.css +27 -0
  530. package/studio/web/styles/components/claude-session.css +212 -0
  531. package/studio/web/styles/components/claude-walking.css +92 -0
  532. package/studio/web/styles/components/clone-voice-save.css +136 -0
  533. package/studio/web/styles/components/clone-voice.css +379 -0
  534. package/studio/web/styles/components/controls.css +70 -0
  535. package/studio/web/styles/components/data-and-image.css +54 -0
  536. package/studio/web/styles/components/forms.css +132 -0
  537. package/studio/web/styles/components/human-banner.css +99 -0
  538. package/studio/web/styles/components/lightbox.css +50 -0
  539. package/studio/web/styles/components/markdown.css +68 -0
  540. package/studio/web/styles/components/modal.css +70 -0
  541. package/studio/web/styles/components/omni-cards.css +105 -0
  542. package/studio/web/styles/components/omni.css +859 -0
  543. package/studio/web/styles/components/omnichat.css +538 -0
  544. package/studio/web/styles/components/panel.css +115 -0
  545. package/studio/web/styles/components/platform-card.css +140 -0
  546. package/studio/web/styles/components/provider-card.css +132 -0
  547. package/studio/web/styles/components/queue.css +185 -0
  548. package/studio/web/styles/components/reference-actions.css +133 -0
  549. package/studio/web/styles/components/reference-picker.css +146 -0
  550. package/studio/web/styles/components/results-drawer.css +224 -0
  551. package/studio/web/styles/components/segmented.css +115 -0
  552. package/studio/web/styles/components/side-panel-tab.css +23 -0
  553. package/studio/web/styles/components/status-signals.css +66 -0
  554. package/studio/web/styles/components/stepper.css +208 -0
  555. package/studio/web/styles/components/table.css +19 -0
  556. package/studio/web/styles/components/tabs.css +24 -0
  557. package/studio/web/styles/components/ui/accordion.css +71 -0
  558. package/studio/web/styles/components/ui/chart.css +162 -0
  559. package/studio/web/styles/components/ui/checkbox.css +37 -0
  560. package/studio/web/styles/components/ui/date-picker.css +153 -0
  561. package/studio/web/styles/components/ui/select.css +110 -0
  562. package/studio/web/styles/components/ui/skeleton.css +25 -0
  563. package/studio/web/styles/components/ui/slider.css +81 -0
  564. package/studio/web/styles/components/ui/switch.css +41 -0
  565. package/studio/web/styles/components/ui/tooltip.css +24 -0
  566. package/studio/web/styles/components/voice-carousel.css +255 -0
  567. package/studio/web/styles/components/voice-editor.css +179 -0
  568. package/studio/web/styles/components/voice-grid.css +56 -0
  569. package/studio/web/styles/components/voice-profiles.css +192 -0
  570. package/studio/web/styles/design-system.md +136 -0
  571. package/studio/web/styles/index.css +116 -0
  572. package/studio/web/styles/layout/page.css +41 -0
  573. package/studio/web/styles/layout/sidebar-groups.css +39 -0
  574. package/studio/web/styles/layout/sidebar.css +111 -0
  575. package/studio/web/styles/pages/agent.css +26 -0
  576. package/studio/web/styles/pages/analytics/dashboard.css +614 -0
  577. package/studio/web/styles/pages/analytics/insights.css +48 -0
  578. package/studio/web/styles/pages/getting-started.css +166 -0
  579. package/studio/web/styles/pages/new-video/form.css +32 -0
  580. package/studio/web/styles/pages/not-found.css +59 -0
  581. package/studio/web/styles/pages/references.css +204 -0
  582. package/studio/web/styles/pages/schedule/calendar.css +541 -0
  583. package/studio/web/styles/pages/schedule/chip-states.css +60 -0
  584. package/studio/web/styles/pages/settings/models.css +72 -0
  585. package/studio/web/styles/pages/settings/shell.css +361 -0
  586. package/studio/web/styles/pages/settings/usage.css +63 -0
  587. package/studio/web/styles/pages/studio-offline.css +105 -0
  588. package/studio/web/styles/pages/video/caption-controls.css +194 -0
  589. package/studio/web/styles/pages/video/captions-grid.css +70 -0
  590. package/studio/web/styles/pages/video/captions.css +79 -0
  591. package/studio/web/styles/pages/video/editor-chat.css +103 -0
  592. package/studio/web/styles/pages/video/export.css +50 -0
  593. package/studio/web/styles/pages/video/frames.css +29 -0
  594. package/studio/web/styles/pages/video/fullscreen-stage.css +400 -0
  595. package/studio/web/styles/pages/video/instagram-carousel.css +309 -0
  596. package/studio/web/styles/pages/video/layout.css +285 -0
  597. package/studio/web/styles/pages/video/narration.css +138 -0
  598. package/studio/web/styles/pages/video/preview-overview.css +11 -0
  599. package/studio/web/styles/pages/video/processing-notice.css +34 -0
  600. package/studio/web/styles/pages/video/publish-when.css +76 -0
  601. package/studio/web/styles/pages/video/publish.css +97 -0
  602. package/studio/web/styles/pages/video/result.css +96 -0
  603. package/studio/web/styles/pages/video/scenes.css +167 -0
  604. package/studio/web/styles/pages/video/sound-timeline.css +672 -0
  605. package/studio/web/styles/pages/video/sound-toolbar.css +149 -0
  606. package/studio/web/styles/pages/video/take-player.css +100 -0
  607. package/studio/web/styles/pages/video/thumbnail.css +157 -0
  608. package/studio/web/styles/pages/video/timeline.css +117 -0
  609. package/studio/web/styles/pages/video/voice-picker.css +14 -0
  610. package/studio/web/styles/pages/video-list/active-row.css +27 -0
  611. package/studio/web/styles/pages/video-list/card-link.css +49 -0
  612. package/studio/web/styles/pages/video-list/list.css +86 -0
  613. package/studio/web/styles/theme/identity/details.css +204 -0
  614. package/studio/web/styles/theme/identity/shell.css +249 -0
  615. package/studio/web/styles/theme/identity/surfaces.css +183 -0
  616. package/studio/web/styles/theme/identity/titles-and-buttons.css +120 -0
  617. package/studio/web/styles/theme/no-borders/blocks.css +208 -0
  618. package/studio/web/styles/theme/no-borders/controls.css +256 -0
  619. package/studio/web/styles/tokens.css +245 -0
  620. package/studio/web/types/api.ts +2 -0
  621. package/studio/web/types/assets.d.ts +9 -0
  622. package/templates/brief.md +27 -0
  623. package/templates/captions.html +204 -0
  624. package/templates/carousel-composition.md +47 -0
  625. package/templates/carousel-script.md +60 -0
  626. package/templates/components/voiceavatar/README.md +87 -0
  627. package/templates/components/voiceavatar/demo.html +50 -0
  628. package/templates/components/voiceavatar/voiceavatar.js +415 -0
  629. package/templates/motion.md +80 -0
  630. package/templates/playbook.md +43 -0
  631. package/templates/reference.md +123 -0
  632. package/templates/scene.html +112 -0
  633. package/templates/scenes.example.json +96 -0
  634. package/templates/script.md +63 -0
  635. package/templates/slide.html +71 -0
  636. package/templates/wireframe.html +91 -0
  637. package/tools/analysis.mjs +300 -0
  638. package/tools/approval.mjs +442 -0
  639. package/tools/assemble.mjs +942 -0
  640. package/tools/build-motion.mjs +437 -0
  641. package/tools/captions.mjs +246 -0
  642. package/tools/carousel.mjs +558 -0
  643. package/tools/compose.mjs +307 -0
  644. package/tools/cuts.mjs +74 -0
  645. package/tools/editor.mjs +419 -0
  646. package/tools/epidemic-capture.js +54 -0
  647. package/tools/fanout.mjs +69 -0
  648. package/tools/history.mjs +70 -0
  649. package/tools/history.py +61 -0
  650. package/tools/job-queue.mjs +180 -0
  651. package/tools/job_queue.py +430 -0
  652. package/tools/lib/agents.mjs +412 -0
  653. package/tools/lib/channel.mjs +399 -0
  654. package/tools/lib/claude-settings.mjs +67 -0
  655. package/tools/lib/cli.mjs +9 -0
  656. package/tools/lib/fanout.mjs +234 -0
  657. package/tools/lib/http.mjs +94 -0
  658. package/tools/lib/index.js +14 -0
  659. package/tools/lib/mcp-server.mjs +69 -0
  660. package/tools/lib/metrics/metrics.mjs +204 -0
  661. package/tools/lib/narration.mjs +15 -0
  662. package/tools/lib/oauth-client.mjs +295 -0
  663. package/tools/lib/omnichat/config.mjs +139 -0
  664. package/tools/lib/omnichat/errors.mjs +42 -0
  665. package/tools/lib/omnichat/extract.mjs +82 -0
  666. package/tools/lib/omnichat/gate.mjs +151 -0
  667. package/tools/lib/omnichat/manual.mjs +166 -0
  668. package/tools/lib/omnichat/match.mjs +78 -0
  669. package/tools/lib/omnichat/pass.mjs +393 -0
  670. package/tools/lib/omnichat/record.mjs +208 -0
  671. package/tools/lib/omnichat/text.mjs +315 -0
  672. package/tools/lib/omnichat/webhook.mjs +166 -0
  673. package/tools/lib/pairing.mjs +99 -0
  674. package/tools/lib/paths.mjs +41 -0
  675. package/tools/lib/platforms/instagram.mjs +469 -0
  676. package/tools/lib/platforms/tiktok.mjs +343 -0
  677. package/tools/lib/platforms/youtube.mjs +228 -0
  678. package/tools/lib/publish/publish.mjs +244 -0
  679. package/tools/lib/schedule/schedule.mjs +191 -0
  680. package/tools/lib/service.mjs +134 -0
  681. package/tools/lib/skill-client.mjs +126 -0
  682. package/tools/lib/skills-release.mjs +196 -0
  683. package/tools/lib/testing.mjs +52 -0
  684. package/tools/lib/up.mjs +187 -0
  685. package/tools/metrics.mjs +415 -0
  686. package/tools/models.mjs +134 -0
  687. package/tools/narrate.mjs +257 -0
  688. package/tools/omni.mjs +298 -0
  689. package/tools/omni_paths.py +12 -0
  690. package/tools/omnichat.mjs +1577 -0
  691. package/tools/omnivideo.py +1122 -0
  692. package/tools/package.mjs +130 -0
  693. package/tools/platforms.mjs +236 -0
  694. package/tools/premiere.mjs +533 -0
  695. package/tools/publish.mjs +1496 -0
  696. package/tools/reference.mjs +1408 -0
  697. package/tools/render.mjs +426 -0
  698. package/tools/runner.mjs +156 -0
  699. package/tools/setup.mjs +466 -0
  700. package/tools/skills-release.mjs +93 -0
  701. package/tools/sound.mjs +1323 -0
  702. package/tools/soundtrack.mjs +217 -0
  703. package/tools/thumbnail.mjs +271 -0
  704. package/tools/timing.mjs +108 -0
  705. package/tools/usage.py +628 -0
  706. package/tools/voice-envelope.mjs +185 -0
  707. package/tools/voice.py +448 -0
  708. package/tools/voice_sample.py +75 -0
  709. package/tools/wireframe.mjs +230 -0
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env node
2
+ /* ============================================================================
3
+ Builds each scene's final HTML from motion/src/<id>.html, with the beat
4
+ times already computed, and optionally renders.
5
+
6
+ node tools/build-motion.mjs videos/<slug> HTML only
7
+ node tools/build-motion.mjs videos/<slug> --stills + a still per scene
8
+ node tools/build-motion.mjs videos/<slug> --sheets + contact sheet at the beats
9
+ node tools/build-motion.mjs videos/<slug> --transitions + a sheet for each cut between scenes
10
+ node tools/build-motion.mjs videos/<slug> --render + final MP4 (needs real timing;
11
+ waits for the machine queue turn)
12
+ node tools/build-motion.mjs videos/<slug> --scene c02 only that scene (and its two cuts); the
13
+ neighbours are built into temporary files, so
14
+ scenes composed in parallel never overwrite
15
+ each other's HTML
16
+
17
+ The source scene never writes seconds for a beat. It uses the beat variable:
18
+
19
+ <div data-anim style="animation-name:rise; --start:var(--b-barra-jev);">
20
+ <div data-anim style="animation-name:rise; --start:calc(var(--b-titulo) + 120ms);">
21
+
22
+ (based on templates/scene.html) and this script injects before </head>:
23
+
24
+ <style id="omnivideo-beats">:root{ --b-titulo:0.4s; --b-barra-jev:5.767s; --scene-dur:7.6s;
25
+ --scene-start:12.4s; --video-dur:41.2s; }</style>
26
+ <script id="omnivideo-data">window.OMNIVIDEO = { scene, start, duration, total_duration, provisional, beats, lines, video }</script>
27
+
28
+ GSAP and Motion read window.OMNIVIDEO.beats to position the paused timeline.
29
+ `video` is the overview of every scene in video seconds (beats, lines and
30
+ words): what a continuous composition uses, drawn once in _common.html and
31
+ positioned at OMNIVIDEO.start + t.
32
+
33
+ Legacy names (scene sources written before the English rename) keep
34
+ working: the PT keys of OMNIVIDEO (cena, inicio, duracao, duracao_total,
35
+ provisorio, falas[{texto, inicio, fim, palavras}], video{duracao_total,
36
+ cenas}), --cena-dur / --cena-inicio, <!-- omnivideo:comum -->,
37
+ motion/src/_comum.html and [data-continua].
38
+
39
+ Continuity between scenes:
40
+ - `--scene-start` is the video second where the scene starts. Background and
41
+ camera that cross the cut move in video time
42
+ (`--start:calc(0s - var(--scene-start))`), not scene time; otherwise they
43
+ restart at every cut.
44
+ - motion/src/_common.html, if present, replaces <!-- omnivideo:common -->
45
+ in every scene: the same background, same code, across the whole video.
46
+ motion/src/_common.<id>.html (a scene's own part, written by that scene
47
+ when the composition runs in parallel) is pasted right after it, in scene
48
+ order, so no two scenes edit the same file.
49
+ - --transitions draws each cut (the outgoing scene at the cut instant | the
50
+ incoming one at its first frame | both overlaid) and, if the scene
51
+ declares `transition: { continues: [...] }`, measures the
52
+ [data-continues] on both sides.
53
+ ============================================================================ */
54
+
55
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, renameSync } from "node:fs";
56
+ import { spawnSync } from "node:child_process";
57
+ import { join, resolve, dirname } from "node:path";
58
+ import { tmpdir } from "node:os";
59
+ import { loadVideo, beatTime } from "./timing.mjs";
60
+ import { turn } from "./job-queue.mjs";
61
+ import { keepPrevious } from "./history.mjs";
62
+ import { CODE_ROOT, DATA_ROOT as ROOT } from "./lib/paths.mjs";
63
+
64
+ const CFG = JSON.parse(readFileSync(join(ROOT, "omnivideo.config.json"), "utf8"));
65
+
66
+ const args = process.argv.slice(2);
67
+ const dir = resolve(args[0] ?? "");
68
+ const flag = n => args.includes(`--${n}`);
69
+ const onlyScene = args.includes("--scene") ? args[args.indexOf("--scene") + 1] : null;
70
+ if (!args[0] || !existsSync(join(dir, "scenes.json"))) {
71
+ console.error(
72
+ "uso: node tools/build-motion.mjs videos/<slug> [--stills] [--sheets] [--transitions] [--render] [--scene cNN]",
73
+ );
74
+ process.exit(2);
75
+ }
76
+
77
+ let video = loadVideo(dir);
78
+ function requireRealTiming() {
79
+ if (flag("render") && video.provisional) {
80
+ console.error(
81
+ "erro: o render final precisa do tempo real da voz. Rode antes:\n" +
82
+ " omnivideo.py voice → omnivideo.py timing (e omnivideo.py captions, para beats em palavras)",
83
+ );
84
+ process.exit(1);
85
+ }
86
+ }
87
+ requireRealTiming();
88
+ /* the final render takes the machine: wait for the queue turn (tools/job-queue.mjs)
89
+ and reload scenes.json, which may have changed while waiting */
90
+ if (flag("render")) {
91
+ await turn("render", dir);
92
+ video = loadVideo(dir);
93
+ requireRealTiming();
94
+ }
95
+ const { w, h, fps } = video.format;
96
+ const HOLD = 0.6;
97
+ const TOL_PX = 2; /* a continuing element may not jump more than this at the cut */
98
+ const MARKER = "<!-- omnivideo:common -->";
99
+ const selected = id => !onlyScene || onlyScene === id;
100
+
101
+ function render(...a) {
102
+ const r = spawnSync(
103
+ "node",
104
+ [
105
+ resolve(CODE_ROOT, CFG.motion.render ?? "tools/render.mjs"),
106
+ ...a,
107
+ "--w",
108
+ String(w),
109
+ "--h",
110
+ String(h),
111
+ "--jobs",
112
+ String(CFG.motion.jobs ?? 3),
113
+ ],
114
+ { stdio: ["ignore", "pipe", "pipe"] },
115
+ );
116
+ if (r.status !== 0) throw new Error(r.stderr.toString() || `render.mjs saiu com ${r.status}`);
117
+ return r.stdout.toString();
118
+ }
119
+
120
+ /* _comum.html = legacy name of _common.html. The scenes' own parts
121
+ (_common.<id>.html, parallel composition) follow it in scene order. */
122
+ const commonFile = ["_common.html", "_comum.html"].map(f => join(dir, "motion", "src", f)).find(existsSync) ?? null;
123
+ const commonParts = [
124
+ ...(commonFile ? [commonFile] : []),
125
+ ...video.scenes.map(s => join(dir, "motion", "src", `_common.${s.id}.html`)).filter(existsSync),
126
+ ];
127
+ const common = commonParts.length
128
+ ? commonParts
129
+ .map(
130
+ (f, i) =>
131
+ (i ? `<!-- omnivideo:common · motion/src/${f.split("/").at(-1)} -->\n` : "") + readFileSync(f, "utf8"),
132
+ )
133
+ .join("\n")
134
+ : null;
135
+ const commonName = commonParts.length ? `motion/src/${commonParts[0].split("/").at(-1)}` : null;
136
+
137
+ /* Overview of the whole video in VIDEO seconds (beats, lines and words), for
138
+ the continuous composition: a system that carries from scene to scene is
139
+ drawn once, in video time, and each scene renders its own window. */
140
+ const abs = (c, s) => +(c.start + s).toFixed(4);
141
+ const overview = {
142
+ total_duration: video.total_duration,
143
+ scenes: video.scenes.map(c => ({
144
+ id: c.id,
145
+ start: c.start,
146
+ duration: c.duration,
147
+ beats: Object.fromEntries((c.beats ?? []).map(b => [b.element, abs(c, beatTime(c, b))])),
148
+ lines: (c.lines ?? []).map(l => ({
149
+ id: l.id,
150
+ text: l.text,
151
+ start: abs(c, l.start),
152
+ end: abs(c, l.end),
153
+ words: l.words?.map(p => ({ w: p.w, s: abs(c, l.start + p.s), e: abs(c, l.start + p.e) })) ?? null,
154
+ })),
155
+ })),
156
+ };
157
+
158
+ /* PT aliases on the same objects, so scene code written before the rename keeps
159
+ working (same values; `beats` is the same in both) */
160
+ const LEGACY_ALIASES =
161
+ "(o=>{const l=x=>Object.assign(x,{texto:x.text,inicio:x.start,fim:x.end,palavras:x.words});" +
162
+ "o.lines.forEach(l);o.video.scenes.forEach(s=>{s.lines.forEach(l);Object.assign(s,{inicio:s.start,duracao:s.duration,falas:s.lines})});" +
163
+ "Object.assign(o.video,{duracao_total:o.video.total_duration,cenas:o.video.scenes});" +
164
+ "Object.assign(o,{cena:o.scene,inicio:o.start,duracao:o.duration,duracao_total:o.total_duration,provisorio:o.provisional,falas:o.lines})})(window.OMNIVIDEO);";
165
+
166
+ /* --- 1. HTML of every scene -------------------------------------------------
167
+ It is just text, and keeps the injected times consistent between neighbours:
168
+ the transitions compare the two. Stills, sheets and render are for the
169
+ selected one. With --scene, only the selected scene's HTML is written in
170
+ place; its neighbours (for the cuts) go to temporary files next to it, which
171
+ keeps relative asset paths and never touches a scene another call may be
172
+ composing or rendering at the same moment. */
173
+ const built = {};
174
+ const scratch = [];
175
+ const sceneIndex = Object.fromEntries(video.scenes.map((s, i) => [s.id, i]));
176
+ const neighbour = id =>
177
+ onlyScene != null && id !== onlyScene && Math.abs(sceneIndex[id] - (sceneIndex[onlyScene] ?? -9)) === 1;
178
+ for (const scene of video.scenes) {
179
+ if (onlyScene && !selected(scene.id) && !neighbour(scene.id)) continue;
180
+ const src = join(dir, "motion", "src", `${scene.id}.html`);
181
+ if (!existsSync(src)) {
182
+ if (selected(scene.id)) {
183
+ console.error(`${scene.id}: falta ${src}`);
184
+ process.exitCode = 1;
185
+ }
186
+ continue;
187
+ }
188
+
189
+ const beats = {};
190
+ const report = [];
191
+ for (const beat of scene.beats ?? []) {
192
+ const t = beatTime(scene, beat);
193
+ beats[beat.element] = t;
194
+ const source =
195
+ beat.enters_with && !video.provisional
196
+ ? beat.enters_with
197
+ : beat.enters_with
198
+ ? `${beat.enters_with} (estimado)`
199
+ : "provisório";
200
+ const slack = scene.duration - t;
201
+ report.push(
202
+ ` ${beat.element.padEnd(18)} ${t.toFixed(2).padStart(6)}s ${source}${slack < HOLD + 0.6 ? " ⚠ pouco tempo até o fim da cena" : ""}`,
203
+ );
204
+ }
205
+
206
+ const vars = Object.entries(beats)
207
+ .map(([k, t]) => `--b-${k}:${t}s;`)
208
+ .join(" ");
209
+ const data = {
210
+ scene: scene.id,
211
+ start: scene.start,
212
+ duration: scene.duration,
213
+ total_duration: video.total_duration,
214
+ provisional: video.provisional,
215
+ beats,
216
+ lines: (scene.lines ?? []).map(l => ({
217
+ id: l.id,
218
+ text: l.text,
219
+ start: l.start,
220
+ end: l.end,
221
+ words: l.words ?? null,
222
+ })),
223
+ video: overview,
224
+ };
225
+ const injection =
226
+ `<style id="omnivideo-beats">:root{ ${vars} --scene-dur:${scene.duration}s; --scene-start:${scene.start}s; --video-dur:${video.total_duration}s;` +
227
+ ` --cena-dur:${scene.duration}s; --cena-inicio:${scene.start}s; }</style>\n` +
228
+ `<script id="omnivideo-data">window.OMNIVIDEO = ${JSON.stringify(data)};\n${LEGACY_ALIASES}</script>\n`;
229
+
230
+ let html = readFileSync(src, "utf8");
231
+ if (!html.includes("</head>")) throw new Error(`${src}: sem </head>`);
232
+ html = html.replace(
233
+ /<style id="omnivideo-beats">[\s\S]*?<\/style>\n?|<script id="omnivideo-(?:data|dados)">[\s\S]*?<\/script>\n?/g,
234
+ "",
235
+ );
236
+ /* replace with a function: line text or code containing `$&` doesn't become a pattern */
237
+ html = html.replace("</head>", () => `${injection}</head>`);
238
+ if (common) {
239
+ /* only the marker alone on its line: quoted in a comment, it doesn't count.
240
+ omnivideo:comum = legacy marker */
241
+ const markerLine = /^[ \t]*<!-- omnivideo:(?:common|comum) -->[ \t]*$/m;
242
+ if (markerLine.test(html))
243
+ html = html.replace(markerLine, () => `<!-- omnivideo:common · ${commonName} -->\n${common}`);
244
+ else if (selected(scene.id))
245
+ console.warn(`${scene.id}: ⚠ existe ${commonName}, mas a cena não tem ${MARKER} sozinho numa linha`);
246
+ }
247
+ /* Components (templates/components/<name>/<name>.js) are pasted in, so the built
248
+ scene is one self-contained file. Marker alone on its line:
249
+ <!-- omnivideo:component voiceavatar --> the component's code
250
+ <!-- omnivideo:envelope --> window.VOICE_ENVELOPE of THIS scene (voice-envelope.mjs),
251
+ null while the scene has no voice yet: the orb idles */
252
+ html = html.replace(/^[ \t]*<!-- omnivideo:component ([\w-]+) -->[ \t]*$/gm, (_, name) => {
253
+ const file = join(CODE_ROOT, "templates", "components", name, `${name}.js`);
254
+ if (!existsSync(file)) {
255
+ console.warn(`${scene.id}: ⚠ componente "${name}" não existe em ${file}`);
256
+ return `<!-- omnivideo:component ${name} · não encontrado -->`;
257
+ }
258
+ const code = readFileSync(file, "utf8");
259
+ /* a literal closing tag anywhere in the code, comments included, ends the inline script */
260
+ if (/<\/script/i.test(code)) throw new Error(`${file}: contém "</script" e quebraria o <script> inline`);
261
+ return `<!-- omnivideo:component ${name} -->\n<script>\n${code}</script>`;
262
+ });
263
+ html = html.replace(/^[ \t]*<!-- omnivideo:envelope -->[ \t]*$/m, () => {
264
+ const r = spawnSync("node", [join(CODE_ROOT, "tools", "voice-envelope.mjs"), "--scene", dir, scene.id], {
265
+ maxBuffer: 1 << 28,
266
+ });
267
+ const env = r.status === 0 ? r.stdout.toString().trim() : "null";
268
+ if (r.status !== 0 && selected(scene.id) && !video.provisional)
269
+ console.warn(
270
+ `${scene.id}: ⚠ sem envelope de voz (${r.stderr.toString().trim()}); o orbe fica só em movimento ocioso`,
271
+ );
272
+ return `<!-- omnivideo:envelope -->\n<script>window.VOICE_ENVELOPE = ${env};</script>`;
273
+ });
274
+ const finalOut = join(dir, scene.html ?? `motion/${scene.id}.html`);
275
+ const out = neighbour(scene.id) ? join(dirname(finalOut), `.${scene.id}.neighbour-${process.pid}.html`) : finalOut;
276
+ if (out !== finalOut) scratch.push(out);
277
+ writeFileSync(out, html);
278
+ built[scene.id] = { out, beats };
279
+
280
+ if (selected(scene.id)) {
281
+ console.log(
282
+ `${scene.id} · ${scene.duration.toFixed(2)}s a partir de ${scene.start.toFixed(2)}s${video.provisional ? " (provisório)" : ""} → ${out}`,
283
+ );
284
+ report.forEach(l => console.log(l));
285
+ }
286
+ }
287
+
288
+ /* --- 2. stills, sheets and render of the selected scene --------------------- */
289
+ mkdirSync(join(dir, "motion", "stills"), { recursive: true });
290
+ const ok = [];
291
+ for (const scene of video.scenes) {
292
+ if (!selected(scene.id) || !built[scene.id]) continue;
293
+ const { out, beats } = built[scene.id];
294
+ const times = Object.values(beats).sort((a, b) => a - b);
295
+ const composition =
296
+ flag("stills") || flag("sheets")
297
+ ? keepPrevious(dir, "composition", scene.id, [
298
+ `motion/stills/${scene.id}.png`,
299
+ `motion/stills/${scene.id}-sheet.png`,
300
+ ])
301
+ : null;
302
+ if (flag("stills")) {
303
+ const t = Math.min((times.at(-1) ?? 0) + HOLD + 0.6, scene.duration - 1 / fps);
304
+ const png = join(dir, "motion", "stills", `${scene.id}.png`);
305
+ render(out, "--still", png, "--at", t.toFixed(3));
306
+ console.log(`${scene.id} · still em ${t.toFixed(2)}s → ${png}`);
307
+ }
308
+ if (flag("sheets")) {
309
+ const marks = [0, ...times.map(t => t + 0.3), scene.duration - 1 / fps]
310
+ .map(t => Math.min(Math.max(t, 0), scene.duration - 1 / fps))
311
+ .filter((t, i, a) => a.indexOf(t) === i)
312
+ .sort((a, b) => a - b);
313
+ const png = join(dir, "motion", "stills", `${scene.id}-sheet.png`);
314
+ render(out, "--sheet", png, "--at", marks.map(t => t.toFixed(2)).join(","));
315
+ console.log(`${scene.id} · folha em ${marks.map(t => t.toFixed(1)).join(", ")}s → ${png}`);
316
+ }
317
+ composition?.close();
318
+ if (flag("render")) {
319
+ const mp4 = join(dir, scene.mp4 ?? `motion/${scene.id}.mp4`);
320
+ const previousRender = keepPrevious(dir, "render", scene.id, [
321
+ scene.mp4 ?? `motion/${scene.id}.mp4`,
322
+ `motion/${scene.id}.render.json`,
323
+ ]);
324
+ render(out, mp4, "--dur", String(scene.duration), "--fps", String(fps));
325
+ /* a scene with its background in video time depends on its own start: if an
326
+ earlier scene changes duration, this one must render again. status and
327
+ assemble compare this record with scenes.json. */
328
+ const videoTime = /var\(--(?:scene-start|cena-inicio)|OMNIVIDEO\??\.(?:start|inicio)/.test(
329
+ readFileSync(out, "utf8"),
330
+ );
331
+ writeFileSync(
332
+ join(dir, "motion", `${scene.id}.render.json`),
333
+ JSON.stringify({ start: scene.start, duration: scene.duration, video_time: videoTime }) + "\n",
334
+ );
335
+ previousRender.close();
336
+ console.log(`${scene.id} · render → ${mp4}${videoTime ? " (fundo no tempo do vídeo)" : ""}`);
337
+ }
338
+ ok.push(scene.id);
339
+ }
340
+
341
+ /* --- 3. transitions ----------------------------------------------------------
342
+ The outgoing scene is captured at t = duration, the cut instant: the frame
343
+ that would come after its last one. If nothing jumps, the incoming one at
344
+ t = 0 matches it in everything that continues, and the overlay comes out sharp. */
345
+ const MEASUREMENTS = "MEASUREMENTS ";
346
+ function readMeasurements(renderOut) {
347
+ const line = renderOut.split("\n").find(l => l.startsWith(MEASUREMENTS));
348
+ return line ? JSON.parse(line.slice(MEASUREMENTS.length)) : [];
349
+ }
350
+
351
+ /* Three panels, always in this order: outgoing | incoming | both at 50 %.
352
+ No labels: this machine's ffmpeg has no drawtext. */
353
+ function transitionSheet(pngA, pngB, out) {
354
+ const width = w >= h ? 640 : 360;
355
+ const graph =
356
+ `[0]split[a0][a1];[1]split[b0][b1];[a1][b1]blend=all_mode=average[m];` +
357
+ `[a0]scale=${width}:-2,pad=iw+8:ih:0:0:white[A];[b0]scale=${width}:-2,pad=iw+8:ih:0:0:white[B];[m]scale=${width}:-2[M];` +
358
+ `[A][B][M]hstack=inputs=3,pad=iw+16:ih+16:8:8:white`;
359
+ const r = spawnSync(
360
+ "ffmpeg",
361
+ ["-y", "-loglevel", "error", "-i", pngA, "-i", pngB, "-filter_complex", graph, "-frames:v", "1", out],
362
+ { stdio: ["ignore", "pipe", "pipe"] },
363
+ );
364
+ if (r.status !== 0) throw new Error(`ffmpeg (folha de passagem): ${r.stderr}`);
365
+ }
366
+
367
+ function transitionKind(p) {
368
+ if (p == null || p === "cut") return { kind: "corte seco", continues: [] };
369
+ if (Array.isArray(p?.continues) && p.continues.length)
370
+ return { kind: `continua: ${p.continues.join(", ")}`, continues: p.continues };
371
+ /* { sound } without continues: hard cut with an effect at the cut (sound.mjs) */
372
+ if (p && typeof p === "object" && p.sound && p.continues == null) return { kind: "corte seco", continues: [] };
373
+ return {
374
+ kind: `⚠ passagem inválida (${JSON.stringify(p)}); use "cut", { "continues": [...] } ou { "sound": ... }`,
375
+ continues: [],
376
+ };
377
+ }
378
+
379
+ if (flag("transitions")) {
380
+ const tmp = join(tmpdir(), `transitions-${process.pid}`);
381
+ mkdirSync(tmp, { recursive: true });
382
+ try {
383
+ for (let i = 1; i < video.scenes.length; i++) {
384
+ const a = video.scenes[i - 1],
385
+ b = video.scenes[i];
386
+ if (onlyScene && onlyScene !== a.id && onlyScene !== b.id) continue;
387
+ if (!built[a.id] || !built[b.id]) {
388
+ console.warn(`${a.id} → ${b.id}: falta o HTML de uma das duas`);
389
+ continue;
390
+ }
391
+ const { kind, continues } = transitionKind(b.transition);
392
+ /* on a hard cut what matters is the outgoing scene's real last frame; when
393
+ it continues, the cut instant (in a continuous composition it is
394
+ already the next one's first frame) */
395
+ const tA = continues.length ? a.duration : Math.max(0, a.duration - 1 / fps);
396
+ const pngA = join(tmp, `${a.id}.png`),
397
+ pngB = join(tmp, `${b.id}.png`);
398
+ const ma = readMeasurements(render(built[a.id].out, "--still", pngA, "--at", tA.toFixed(4), "--measure"));
399
+ const mb = readMeasurements(render(built[b.id].out, "--still", pngB, "--at", "0", "--measure"));
400
+ const png = join(dir, "motion", "stills", `transition-${a.id}-${b.id}.png`);
401
+ const previousCut = keepPrevious(dir, "composition", `transition-${a.id}-${b.id}`, [
402
+ `motion/stills/transition-${a.id}-${b.id}.png`,
403
+ ]);
404
+ /* rendered aside and renamed: two scene calls running in parallel share this cut */
405
+ const pngTmp = join(dirname(png), `.transition-${a.id}-${b.id}-${process.pid}.png`);
406
+ transitionSheet(pngA, pngB, pngTmp);
407
+ renameSync(pngTmp, png);
408
+ previousCut.close();
409
+
410
+ console.log(`${a.id} → ${b.id} · ${kind} → ${png}`);
411
+ for (const name of continues) {
412
+ const x = ma.find(m => m.name === name),
413
+ y = mb.find(m => m.name === name);
414
+ if (!x || !y) {
415
+ console.log(
416
+ ` ${name.padEnd(18)} ⚠ falta [data-continues="${name}"] em ${[!x && a.id, !y && b.id].filter(Boolean).join(" e ")}`,
417
+ );
418
+ process.exitCode = 1;
419
+ continue;
420
+ }
421
+ const d = Math.max(Math.abs(x.x - y.x), Math.abs(x.y - y.y), Math.abs(x.w - y.w), Math.abs(x.h - y.h));
422
+ const dOp = Math.abs(x.opacity - y.opacity);
423
+ const jumps = d > TOL_PX || dOp > 0.05;
424
+ console.log(
425
+ ` ${name.padEnd(18)} desvio ${d.toFixed(1)} px · opacidade ${x.opacity.toFixed(2)} → ${y.opacity.toFixed(2)}` +
426
+ (jumps ? " ⚠ pula no corte" : " ok"),
427
+ );
428
+ if (jumps) process.exitCode = 1;
429
+ }
430
+ }
431
+ } finally {
432
+ rmSync(tmp, { recursive: true, force: true });
433
+ }
434
+ }
435
+
436
+ for (const f of scratch) rmSync(f, { force: true });
437
+ if (!ok.length) process.exitCode = 1;
@@ -0,0 +1,246 @@
1
+ /* ============================================================================
2
+ Burned-in captions: where they come from and where they sit. Used by
3
+ assemble.mjs (render) and by the studio (preview), so both see the same HTML.
4
+
5
+ Each caption in captions.srt gets an id `<line>.<n>` (the n-th caption of
6
+ that line; a caption never spans two lines) and its scene.
7
+
8
+ The position lives in `captions` in scenes.json, on three levels, each one
9
+ over the previous:
10
+
11
+ "captions": {
12
+ "platform": "all", safe area: shorts, tiktok, reels, all (9:16) · youtube (16:9)
13
+ "anchor": "bottom-center", 3×3 grid: top|middle|bottom - left|center|right
14
+ "x": 0, "y": 0, offset in frame px (+x right, +y down)
15
+ "style": "shadow", shadow | pill (default in the config's captions.style)
16
+ "safe": { "bottom": 0.12 }, this video's safe area tweak, per side (fraction of the frame)
17
+ "color": "#ffd400", text color
18
+ "background": "#000000", "background_opacity": 0.7, pill: background color and opacity
19
+ "outline": 4, "outline_color": "#000000", text outline: width (frame px) and color
20
+ "shadow_color": "#000000", "shadow_opacity": 0.5, shadow: color, opacity,
21
+ "shadow_blur": 12, "shadow_x": 0, "shadow_y": 6 blur and offset (frame px)
22
+ "scenes": { "c04": { "anchor": "top-center", "y": 40 } },
23
+ "individual": { "f03.2": { "x": -30 }, "f04.1": { "hidden": true } }
24
+ }
25
+
26
+ The anchor pins the caption box to the edge (or center) of the safe area;
27
+ the offset moves from there. A caption may go outside the safe area: only
28
+ the frame limits it, and what goes past is flagged (`outside`, with side and
29
+ px). Each platform's safe area is in the config's `captions.safe_area`, as a
30
+ fraction of the frame; `all` is the most restrictive on each side. `safe` in
31
+ scenes.json overrides only the sides it lists, for that video.
32
+
33
+ `hidden: true` on an individual caption turns it off: it stays in
34
+ captions.srt, but assemble doesn't burn it (captionsOf flags `hidden`, and
35
+ assemble drops it). Without the key, the caption shows.
36
+
37
+ A caption's text can be edited in the UI: the new one goes straight to
38
+ captions.srt (that's what assemble and Premiere read), and `captions.texts`
39
+ keeps the swap so the next generation doesn't lose it:
40
+
41
+ "texts": { "f03.2": { "text": "de 70 a 500 ms", "original": "de 70 a 500 milissegundos" } }
42
+
43
+ `original` is what the line generated. `omnivideo.py captions` puts the
44
+ edited text back on the caption of the same line that still generates the
45
+ same `original`; if the line changed and no longer does, the edit is
46
+ dropped, with a warning.
47
+
48
+ Color, outline and shadow follow the same three levels as the position
49
+ (video → scene → caption): a key absent from every level gets the style's
50
+ default (`styleDefaults`). Touching any shadow key replaces the style's
51
+ shadow with a single layer, with the five values.
52
+ ============================================================================ */
53
+
54
+ import { readFileSync, existsSync, writeFileSync, renameSync } from "node:fs";
55
+ import { join } from "node:path";
56
+ import { CODE_ROOT, DATA_ROOT as ROOT } from "./lib/paths.mjs";
57
+
58
+ const CFG = JSON.parse(readFileSync(join(ROOT, "omnivideo.config.json"), "utf8"));
59
+
60
+ export const ROWS = ["top", "middle", "bottom"];
61
+ export const COLUMNS = ["left", "center", "right"];
62
+ export const ANCHORS = ROWS.flatMap(r => COLUMNS.map(c => `${r}-${c}`));
63
+ export const STYLES = ["pill", "shadow"];
64
+ export const SIDES = ["top", "bottom", "left", "right"];
65
+
66
+ /* color, outline and shadow: key → { type, range } (color as #rrggbb; the rest, a number) */
67
+ export const STYLE_KEYS = [
68
+ "color",
69
+ "background",
70
+ "background_opacity",
71
+ "outline",
72
+ "outline_color",
73
+ "shadow_color",
74
+ "shadow_opacity",
75
+ "shadow_blur",
76
+ "shadow_x",
77
+ "shadow_y",
78
+ ];
79
+ export const STYLE_LIMITS = {
80
+ background_opacity: [0, 1, 2],
81
+ outline: [0, 40, 0],
82
+ shadow_opacity: [0, 1, 2],
83
+ shadow_blur: [0, 100, 0],
84
+ shadow_x: [-100, 100, 0],
85
+ shadow_y: [-100, 100, 0] /* [min, max, decimals] */,
86
+ };
87
+ export const STYLE_COLORS = ["color", "background", "outline_color", "shadow_color"];
88
+
89
+ /* what each style draws untouched, in frame px (same as the template's CSS) */
90
+ export function styleDefaults(format) {
91
+ const body = ((aspectOf(format) === "16:9" ? 2.4 : 4.4) / 100) * format.w;
92
+ const vw = n => Math.round((n / 100) * format.w);
93
+ const common = {
94
+ background: "#ffffff",
95
+ background_opacity: 0.9,
96
+ outline: 0,
97
+ outline_color: "#000000",
98
+ shadow_color: "#000000",
99
+ shadow_x: 0,
100
+ };
101
+ return {
102
+ pill: { ...common, color: "#111111", shadow_opacity: 0.18, shadow_blur: vw(2.4), shadow_y: vw(0.4) },
103
+ shadow: {
104
+ ...common,
105
+ color: "#ffffff",
106
+ shadow_opacity: 0.55,
107
+ shadow_blur: Math.round(0.12 * body),
108
+ shadow_y: Math.round(0.04 * body),
109
+ },
110
+ };
111
+ }
112
+
113
+ export const aspectOf = format => format.aspect ?? (format.w < format.h ? "9:16" : "16:9");
114
+
115
+ /* a caption's character limit (past it, the template breaks into two lines) */
116
+ export const maxChars = format => CFG.captions?.max_chars?.[aspectOf(format)] ?? null;
117
+
118
+ /* the format's safe areas, plus `all` (the largest inset on each side) */
119
+ export function safeAreas(format) {
120
+ const table = { ...(CFG.captions?.safe_area?.[aspectOf(format)] ?? {}) };
121
+ const vals = Object.values(table);
122
+ if (vals.length > 1) table.all = Object.fromEntries(SIDES.map(s => [s, Math.max(...vals.map(v => v[s] ?? 0))]));
123
+ return table;
124
+ }
125
+
126
+ /* the video's position with the config defaults underneath */
127
+ export function positionOf(video) {
128
+ const defaults = {
129
+ platform: "all",
130
+ anchor: "bottom-center",
131
+ x: 0,
132
+ y: 0,
133
+ style: CFG.captions?.style ?? "shadow",
134
+ ...(CFG.captions?.default_position ?? {}),
135
+ };
136
+ const p = { ...defaults, ...(video.captions ?? {}) };
137
+ const safes = safeAreas(video.format);
138
+ if (!safes[p.platform]) p.platform = Object.keys(safes)[0] ?? "all";
139
+ return p;
140
+ }
141
+
142
+ const srtTime = s => {
143
+ const [h, m, r] = s.trim().replace(",", ".").split(":");
144
+ return +h * 3600 + +m * 60 + +r;
145
+ };
146
+
147
+ export function readSrt(file) {
148
+ return readFileSync(file, "utf8")
149
+ .replace(/\r/g, "")
150
+ .split(/\n\s*\n/)
151
+ .flatMap(block => {
152
+ const rows = block.trim().split("\n");
153
+ const i = rows.findIndex(l => l.includes("-->"));
154
+ if (i < 0) return [];
155
+ const [a, b] = rows[i].split("-->");
156
+ const text = rows
157
+ .slice(i + 1)
158
+ .join(" ")
159
+ .trim();
160
+ return text ? [{ s: srtTime(a), e: srtTime(b), text }] : [];
161
+ });
162
+ }
163
+
164
+ /* replaces the text of the `index`-th caption (readSrt order), leaving the rest of the file untouched */
165
+ export function replaceSrtText(file, index, text) {
166
+ let k = -1;
167
+ const blocks = readFileSync(file, "utf8")
168
+ .replace(/\r/g, "")
169
+ .split(/\n\s*\n/)
170
+ .map(block => {
171
+ const rows = block.trim().split("\n");
172
+ const i = rows.findIndex(l => l.includes("-->"));
173
+ if (
174
+ i < 0 ||
175
+ !rows
176
+ .slice(i + 1)
177
+ .join(" ")
178
+ .trim()
179
+ )
180
+ return block.trim();
181
+ k += 1;
182
+ return k === index ? [...rows.slice(0, i + 1), text].join("\n") : block.trim();
183
+ });
184
+ if (index > k) throw new Error(`o captions.srt não tem a legenda ${index + 1}`);
185
+ const tmp = `${file}.${process.pid}.tmp`;
186
+ writeFileSync(tmp, blocks.filter(Boolean).join("\n\n") + "\n");
187
+ renameSync(tmp, file);
188
+ }
189
+
190
+ /* [{ id, scene, line, s, e, text }] in video time; `video` comes from loadVideo() */
191
+ export function captionsOf(dir, video) {
192
+ const file = join(dir, "captions.srt");
193
+ if (!existsSync(file)) return [];
194
+ const lines = video.scenes.flatMap(sc =>
195
+ (sc.lines ?? []).map(l => ({ id: l.id, scene: sc.id, a: sc.start + l.start, b: sc.start + l.end })),
196
+ );
197
+ const count = {};
198
+ return readSrt(file).map((cue, k) => {
199
+ const mid = (cue.s + cue.e) / 2;
200
+ const l =
201
+ lines.find(x => mid >= x.a - 0.05 && mid <= x.b + 0.05) ??
202
+ lines.reduce((m, x) => (Math.abs((x.a + x.b) / 2 - mid) < Math.abs((m.a + m.b) / 2 - mid) ? x : m), lines[0]);
203
+ const scene = l?.scene ?? video.scenes.find(sc => mid >= sc.start && mid < sc.start + sc.duration)?.id ?? null;
204
+ const base = l?.id ?? `l${k + 1}`;
205
+ count[base] = (count[base] ?? 0) + 1;
206
+ const id = l ? `${base}.${count[base]}` : base;
207
+ /* edited in the UI: `original` is what the line generated */
208
+ const ed = video.captions?.texts?.[id];
209
+ return {
210
+ id,
211
+ scene,
212
+ line: l?.id ?? null,
213
+ s: +cue.s.toFixed(3),
214
+ e: +cue.e.toFixed(3),
215
+ text: cue.text,
216
+ hidden: video.captions?.individual?.[id]?.hidden === true,
217
+ ...(ed?.text === cue.text && ed.original ? { original: ed.original } : {}),
218
+ };
219
+ });
220
+ }
221
+
222
+ /* templates/captions.html with the data, the position and the video's _captions.css */
223
+ export function captionsHtml(dir, video, captions = captionsOf(dir, video)) {
224
+ const pos = positionOf(video);
225
+ const { w, h } = video.format;
226
+ const cssFile = join(dir, "motion", "src", "_captions.css");
227
+ const css = existsSync(cssFile) ? readFileSync(cssFile, "utf8") : "";
228
+ const data = {
229
+ w,
230
+ h,
231
+ safes: safeAreas(video.format),
232
+ position: pos,
233
+ keys: ["anchor", "x", "y", ...STYLE_KEYS],
234
+ defaults: styleDefaults(video.format),
235
+ captions,
236
+ };
237
+ const json = JSON.stringify(data).replace(/</g, "\\u003c");
238
+ const html = readFileSync(join(CODE_ROOT, "templates", "captions.html"), "utf8")
239
+ .replace(
240
+ '<html lang="pt-BR">',
241
+ `<html lang="pt-BR" data-format="${aspectOf(video.format)}" data-style="${pos.style}">`,
242
+ )
243
+ .replace(/\/\*omnivideo:data\*\/\{[\s\S]*?\};/, () => `${json};`)
244
+ .replace("/*omnivideo:video-style*/", () => css);
245
+ return { html, pos, css: !!css };
246
+ }