@crievo/omni 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (708) hide show
  1. package/.env.example +41 -0
  2. package/README.md +2 -2
  3. package/bin/omni.mjs +18 -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.tsx +59 -0
  247. package/studio/web/hooks/useAccounts.ts +27 -0
  248. package/studio/web/hooks/useAdmin.ts +12 -0
  249. package/studio/web/hooks/useAnalytics.ts +21 -0
  250. package/studio/web/hooks/useAvatarAudioReactive.ts +87 -0
  251. package/studio/web/hooks/useBrowserConnect.ts +48 -0
  252. package/studio/web/hooks/useClonePlan.ts +14 -0
  253. package/studio/web/hooks/useConnected.ts +7 -0
  254. package/studio/web/hooks/useLiveQuery.ts +29 -0
  255. package/studio/web/hooks/useMenuCollapsed.ts +32 -0
  256. package/studio/web/hooks/useMicDevices.ts +53 -0
  257. package/studio/web/hooks/useMissingDms.ts +211 -0
  258. package/studio/web/hooks/useMissingSetupSteps.ts +26 -0
  259. package/studio/web/hooks/useNow.ts +11 -0
  260. package/studio/web/hooks/useOmniAgent.ts +278 -0
  261. package/studio/web/hooks/useOmniClaude.ts +124 -0
  262. package/studio/web/hooks/useOmniDock.ts +142 -0
  263. package/studio/web/hooks/useOmniGreeting.ts +60 -0
  264. package/studio/web/hooks/useOmniHotword.ts +128 -0
  265. package/studio/web/hooks/useOmnichat.ts +26 -0
  266. package/studio/web/hooks/useOnboarding.ts +27 -0
  267. package/studio/web/hooks/usePost.ts +23 -0
  268. package/studio/web/hooks/useQueue.ts +10 -0
  269. package/studio/web/hooks/useReferences.ts +20 -0
  270. package/studio/web/hooks/useRuleEditor.ts +101 -0
  271. package/studio/web/hooks/useRunner.ts +24 -0
  272. package/studio/web/hooks/useSaveAction.ts +23 -0
  273. package/studio/web/hooks/useSchedule.ts +11 -0
  274. package/studio/web/hooks/useServices.ts +10 -0
  275. package/studio/web/hooks/useSessions.ts +15 -0
  276. package/studio/web/hooks/useSettings.ts +11 -0
  277. package/studio/web/hooks/useSettingsSection.ts +30 -0
  278. package/studio/web/hooks/useSidePanel.ts +23 -0
  279. package/studio/web/hooks/useSound.ts +30 -0
  280. package/studio/web/hooks/useUsage.ts +11 -0
  281. package/studio/web/hooks/useVideoVoice.ts +36 -0
  282. package/studio/web/hooks/useVideos.ts +21 -0
  283. package/studio/web/hooks/useVoiceProfilePreview.ts +52 -0
  284. package/studio/web/hooks/useVoiceProfiles.ts +7 -0
  285. package/studio/web/hooks/useVoiceRunning.ts +10 -0
  286. package/studio/web/hooks/useVoiceStudioClosed.ts +8 -0
  287. package/studio/web/index.html +19 -0
  288. package/studio/web/lib/agent.ts +7 -0
  289. package/studio/web/lib/alerts.ts +210 -0
  290. package/studio/web/lib/analytics-metrics.ts +351 -0
  291. package/studio/web/lib/api.ts +70 -0
  292. package/studio/web/lib/audio.ts +39 -0
  293. package/studio/web/lib/avatars.ts +41 -0
  294. package/studio/web/lib/calendar.ts +218 -0
  295. package/studio/web/lib/caption-position.ts +249 -0
  296. package/studio/web/lib/carousel.ts +24 -0
  297. package/studio/web/lib/chime.ts +42 -0
  298. package/studio/web/lib/clone-voice.ts +55 -0
  299. package/studio/web/lib/export.ts +11 -0
  300. package/studio/web/lib/files.ts +8 -0
  301. package/studio/web/lib/format.ts +93 -0
  302. package/studio/web/lib/media.ts +29 -0
  303. package/studio/web/lib/mic-device.ts +72 -0
  304. package/studio/web/lib/missing-dms.ts +70 -0
  305. package/studio/web/lib/omni-cards.ts +42 -0
  306. package/studio/web/lib/omni-cascade.ts +1067 -0
  307. package/studio/web/lib/omni-greeting-audio.ts +24 -0
  308. package/studio/web/lib/omni-hotword.ts +231 -0
  309. package/studio/web/lib/omni-panel.ts +67 -0
  310. package/studio/web/lib/omni-realtime.ts +811 -0
  311. package/studio/web/lib/omni-ui.ts +402 -0
  312. package/studio/web/lib/omni-wait.ts +174 -0
  313. package/studio/web/lib/omnichat-api.ts +44 -0
  314. package/studio/web/lib/orb.ts +270 -0
  315. package/studio/web/lib/pcm-recorder.ts +115 -0
  316. package/studio/web/lib/platforms.ts +17 -0
  317. package/studio/web/lib/providers.ts +27 -0
  318. package/studio/web/lib/publish-draft.ts +52 -0
  319. package/studio/web/lib/publish-settings.ts +81 -0
  320. package/studio/web/lib/query-client.ts +122 -0
  321. package/studio/web/lib/references.ts +13 -0
  322. package/studio/web/lib/services.ts +40 -0
  323. package/studio/web/lib/sessions.ts +103 -0
  324. package/studio/web/lib/settings-categories.ts +58 -0
  325. package/studio/web/lib/sound-audio.ts +33 -0
  326. package/studio/web/lib/sound-timeline.ts +291 -0
  327. package/studio/web/lib/sse.ts +86 -0
  328. package/studio/web/lib/stage-history.ts +141 -0
  329. package/studio/web/lib/stages.ts +344 -0
  330. package/studio/web/lib/studio-offline.ts +60 -0
  331. package/studio/web/lib/upload.ts +24 -0
  332. package/studio/web/lib/video-tabs.ts +118 -0
  333. package/studio/web/lib/voice-profile-draft.ts +34 -0
  334. package/studio/web/lib/voice-profiles.ts +3 -0
  335. package/studio/web/main.tsx +14 -0
  336. package/studio/web/pages/AdminPage/index.tsx +123 -0
  337. package/studio/web/pages/AgentPage/components/Measurements.tsx +102 -0
  338. package/studio/web/pages/AgentPage/index.tsx +28 -0
  339. package/studio/web/pages/AnalyticsPage/components/About.tsx +52 -0
  340. package/studio/web/pages/AnalyticsPage/components/ChannelCards.tsx +106 -0
  341. package/studio/web/pages/AnalyticsPage/components/ChannelView.tsx +137 -0
  342. package/studio/web/pages/AnalyticsPage/components/CollectionStatus.tsx +46 -0
  343. package/studio/web/pages/AnalyticsPage/components/Comparison.tsx +68 -0
  344. package/studio/web/pages/AnalyticsPage/components/Diagnosis.tsx +50 -0
  345. package/studio/web/pages/AnalyticsPage/components/Highlights.tsx +49 -0
  346. package/studio/web/pages/AnalyticsPage/components/Hint.tsx +11 -0
  347. package/studio/web/pages/AnalyticsPage/components/HookVsBody.tsx +100 -0
  348. package/studio/web/pages/AnalyticsPage/components/HourlyWave.tsx +133 -0
  349. package/studio/web/pages/AnalyticsPage/components/Insights.tsx +70 -0
  350. package/studio/web/pages/AnalyticsPage/components/MetricCard.tsx +46 -0
  351. package/studio/web/pages/AnalyticsPage/components/PlanSection.tsx +40 -0
  352. package/studio/web/pages/AnalyticsPage/components/ProgressionChart.tsx +155 -0
  353. package/studio/web/pages/AnalyticsPage/components/ReelBarsChart.tsx +154 -0
  354. package/studio/web/pages/AnalyticsPage/components/ReelThumb.tsx +19 -0
  355. package/studio/web/pages/AnalyticsPage/components/ReelView.tsx +290 -0
  356. package/studio/web/pages/AnalyticsPage/components/ReelsTable.tsx +269 -0
  357. package/studio/web/pages/AnalyticsPage/components/RequestWithFocus.tsx +38 -0
  358. package/studio/web/pages/AnalyticsPage/components/SegmentedControl.tsx +35 -0
  359. package/studio/web/pages/AnalyticsPage/components/VsMedian.tsx +31 -0
  360. package/studio/web/pages/AnalyticsPage/index.tsx +17 -0
  361. package/studio/web/pages/GettingStartedPage/index.tsx +174 -0
  362. package/studio/web/pages/NewVideoPage/components/DeliverablesPicker.tsx +62 -0
  363. package/studio/web/pages/NewVideoPage/components/FormatPicker.tsx +33 -0
  364. package/studio/web/pages/NewVideoPage/index.tsx +133 -0
  365. package/studio/web/pages/NotFoundPage/index.tsx +9 -0
  366. package/studio/web/pages/OmnichatPage/components/NewFromLink.tsx +59 -0
  367. package/studio/web/pages/OmnichatPage/components/RuleListItem.tsx +64 -0
  368. package/studio/web/pages/OmnichatPage/components/RulePreview.tsx +14 -0
  369. package/studio/web/pages/OmnichatPage/components/RuleView.tsx +88 -0
  370. package/studio/web/pages/OmnichatPage/index.tsx +153 -0
  371. package/studio/web/pages/QueuePage/index.tsx +22 -0
  372. package/studio/web/pages/ReferencePage/components/ReferenceTexts.tsx +131 -0
  373. package/studio/web/pages/ReferencePage/index.tsx +216 -0
  374. package/studio/web/pages/ReferencesPage/components/NewReferenceForm.tsx +150 -0
  375. package/studio/web/pages/ReferencesPage/components/ReferenceCard.tsx +42 -0
  376. package/studio/web/pages/ReferencesPage/components/ReferenceGridSkeleton.tsx +31 -0
  377. package/studio/web/pages/ReferencesPage/index.tsx +67 -0
  378. package/studio/web/pages/SchedulePage/components/CalendarSkeleton.tsx +18 -0
  379. package/studio/web/pages/SchedulePage/components/DayView.tsx +177 -0
  380. package/studio/web/pages/SchedulePage/components/HourPicker.tsx +103 -0
  381. package/studio/web/pages/SchedulePage/components/MonthDayCell.tsx +111 -0
  382. package/studio/web/pages/SchedulePage/components/MonthView.tsx +47 -0
  383. package/studio/web/pages/SchedulePage/components/PlatformIcons.tsx +12 -0
  384. package/studio/web/pages/SchedulePage/components/PostThumb.tsx +15 -0
  385. package/studio/web/pages/SchedulePage/components/WeekColumn.tsx +75 -0
  386. package/studio/web/pages/SchedulePage/components/WeekView.tsx +35 -0
  387. package/studio/web/pages/SchedulePage/index.tsx +285 -0
  388. package/studio/web/pages/SettingsPage/components/AgentAccounts.tsx +263 -0
  389. package/studio/web/pages/SettingsPage/components/AgentCategory.tsx +20 -0
  390. package/studio/web/pages/SettingsPage/components/ModelPicker.tsx +49 -0
  391. package/studio/web/pages/SettingsPage/components/ModelsSettings.tsx +138 -0
  392. package/studio/web/pages/SettingsPage/components/PlatformsCategory.tsx +23 -0
  393. package/studio/web/pages/SettingsPage/components/PublishingCategory/AccountStatus.tsx +29 -0
  394. package/studio/web/pages/SettingsPage/components/PublishingCategory/AutoPublishPanel.tsx +90 -0
  395. package/studio/web/pages/SettingsPage/components/PublishingCategory/BrowserConnect.tsx +62 -0
  396. package/studio/web/pages/SettingsPage/components/PublishingCategory/CheckField.tsx +19 -0
  397. package/studio/web/pages/SettingsPage/components/PublishingCategory/InstagramPanel.tsx +113 -0
  398. package/studio/web/pages/SettingsPage/components/PublishingCategory/OmnichatSettingsPanel.tsx +146 -0
  399. package/studio/web/pages/SettingsPage/components/PublishingCategory/SaveBar.tsx +33 -0
  400. package/studio/web/pages/SettingsPage/components/PublishingCategory/SchedulePanel.tsx +132 -0
  401. package/studio/web/pages/SettingsPage/components/PublishingCategory/SettingsCard.tsx +47 -0
  402. package/studio/web/pages/SettingsPage/components/PublishingCategory/SettingsCardGroup.tsx +19 -0
  403. package/studio/web/pages/SettingsPage/components/PublishingCategory/TextPanel.tsx +55 -0
  404. package/studio/web/pages/SettingsPage/components/PublishingCategory/TiktokPanel.tsx +123 -0
  405. package/studio/web/pages/SettingsPage/components/PublishingCategory/YoutubePanel.tsx +100 -0
  406. package/studio/web/pages/SettingsPage/components/PublishingCategory/badges.tsx +15 -0
  407. package/studio/web/pages/SettingsPage/components/PublishingCategory/index.tsx +40 -0
  408. package/studio/web/pages/SettingsPage/components/PublishingCategory/types.ts +10 -0
  409. package/studio/web/pages/SettingsPage/components/UsageCategory.tsx +389 -0
  410. package/studio/web/pages/SettingsPage/components/VoiceProvidersCategory.tsx +97 -0
  411. package/studio/web/pages/SettingsPage/index.tsx +85 -0
  412. package/studio/web/pages/VideoPage/components/AudioPlayer.tsx +113 -0
  413. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/AxisField.tsx +50 -0
  414. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionList.tsx +56 -0
  415. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionListItem.tsx +92 -0
  416. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionMargins.tsx +57 -0
  417. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionStyleFields.tsx +70 -0
  418. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionTextForm.tsx +87 -0
  419. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/ColorField.tsx +37 -0
  420. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/MarginField.tsx +59 -0
  421. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/NumberField.tsx +57 -0
  422. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/PositionControls.tsx +140 -0
  423. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/captionEditor.ts +215 -0
  424. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/useCaptionPosition.ts +154 -0
  425. package/studio/web/pages/VideoPage/components/CarouselChangeRequest.tsx +72 -0
  426. package/studio/web/pages/VideoPage/components/CarouselCompositionStage.tsx +80 -0
  427. package/studio/web/pages/VideoPage/components/CarouselCreate.tsx +63 -0
  428. package/studio/web/pages/VideoPage/components/CarouselMissing.tsx +19 -0
  429. package/studio/web/pages/VideoPage/components/CarouselPublishStage.tsx +173 -0
  430. package/studio/web/pages/VideoPage/components/CarouselScriptStage.tsx +82 -0
  431. package/studio/web/pages/VideoPage/components/CarouselSlideCard.tsx +49 -0
  432. package/studio/web/pages/VideoPage/components/CarouselViewer.tsx +55 -0
  433. package/studio/web/pages/VideoPage/components/ChangedVoices.tsx +53 -0
  434. package/studio/web/pages/VideoPage/components/CompositionStage.tsx +78 -0
  435. package/studio/web/pages/VideoPage/components/CoverPicker.tsx +32 -0
  436. package/studio/web/pages/VideoPage/components/EditorStage.tsx +53 -0
  437. package/studio/web/pages/VideoPage/components/LineTakes.tsx +160 -0
  438. package/studio/web/pages/VideoPage/components/LineTextEditor.tsx +124 -0
  439. package/studio/web/pages/VideoPage/components/LineVoicePicker.tsx +37 -0
  440. package/studio/web/pages/VideoPage/components/MediaImage.tsx +25 -0
  441. package/studio/web/pages/VideoPage/components/MotionScriptStage.tsx +39 -0
  442. package/studio/web/pages/VideoPage/components/NarrationStage.tsx +70 -0
  443. package/studio/web/pages/VideoPage/components/PublishForm.tsx +387 -0
  444. package/studio/web/pages/VideoPage/components/PublishStage.tsx +76 -0
  445. package/studio/web/pages/VideoPage/components/PublishText.tsx +149 -0
  446. package/studio/web/pages/VideoPage/components/ScenesApprovalPanel.tsx +278 -0
  447. package/studio/web/pages/VideoPage/components/ScheduledInfo.tsx +72 -0
  448. package/studio/web/pages/VideoPage/components/ScriptApprovalPanel.tsx +189 -0
  449. package/studio/web/pages/VideoPage/components/ScriptStage.tsx +17 -0
  450. package/studio/web/pages/VideoPage/components/SequencePlayer.tsx +146 -0
  451. package/studio/web/pages/VideoPage/components/SoundTimeline/AddSound.tsx +117 -0
  452. package/studio/web/pages/VideoPage/components/SoundTimeline/AreaSelection.tsx +74 -0
  453. package/studio/web/pages/VideoPage/components/SoundTimeline/BedPanel.tsx +94 -0
  454. package/studio/web/pages/VideoPage/components/SoundTimeline/BedRow.tsx +45 -0
  455. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionOverlay.tsx +44 -0
  456. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionSaveBar.tsx +32 -0
  457. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionsPanel.tsx +22 -0
  458. package/studio/web/pages/VideoPage/components/SoundTimeline/ClipBlock.tsx +111 -0
  459. package/studio/web/pages/VideoPage/components/SoundTimeline/ClipPanel.tsx +76 -0
  460. package/studio/web/pages/VideoPage/components/SoundTimeline/CutsPanel.tsx +44 -0
  461. package/studio/web/pages/VideoPage/components/SoundTimeline/EditorTabBar.tsx +73 -0
  462. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectChoice.tsx +46 -0
  463. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectItem.tsx +89 -0
  464. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectPanel.tsx +232 -0
  465. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectsRow.tsx +21 -0
  466. package/studio/web/pages/VideoPage/components/SoundTimeline/Lane.tsx +39 -0
  467. package/studio/web/pages/VideoPage/components/SoundTimeline/MarkerRow.tsx +40 -0
  468. package/studio/web/pages/VideoPage/components/SoundTimeline/MuteButton.tsx +36 -0
  469. package/studio/web/pages/VideoPage/components/SoundTimeline/NumberInput.tsx +37 -0
  470. package/studio/web/pages/VideoPage/components/SoundTimeline/PlanWarnings.tsx +19 -0
  471. package/studio/web/pages/VideoPage/components/SoundTimeline/PlayerResizeHandle.tsx +50 -0
  472. package/studio/web/pages/VideoPage/components/SoundTimeline/PreviewControls.tsx +51 -0
  473. package/studio/web/pages/VideoPage/components/SoundTimeline/SceneRow.tsx +54 -0
  474. package/studio/web/pages/VideoPage/components/SoundTimeline/SettingsPanel.tsx +65 -0
  475. package/studio/web/pages/VideoPage/components/SoundTimeline/SoundLibrary.tsx +99 -0
  476. package/studio/web/pages/VideoPage/components/SoundTimeline/SoundPreview.tsx +82 -0
  477. package/studio/web/pages/VideoPage/components/SoundTimeline/SpeedPanel.tsx +128 -0
  478. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineDock.tsx +56 -0
  479. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineFooter.tsx +26 -0
  480. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineSkeleton.tsx +21 -0
  481. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineToolbar.tsx +160 -0
  482. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineTracks.tsx +121 -0
  483. package/studio/web/pages/VideoPage/components/SoundTimeline/TrackSidebar.tsx +30 -0
  484. package/studio/web/pages/VideoPage/components/SoundTimeline/VoiceRow.tsx +53 -0
  485. package/studio/web/pages/VideoPage/components/SoundTimeline/Waveform.tsx +97 -0
  486. package/studio/web/pages/VideoPage/components/SoundTimeline/clipActions.ts +50 -0
  487. package/studio/web/pages/VideoPage/components/SoundTimeline/effectActions.ts +161 -0
  488. package/studio/web/pages/VideoPage/components/SoundTimeline/index.tsx +161 -0
  489. package/studio/web/pages/VideoPage/components/SoundTimeline/timelineGeometry.ts +45 -0
  490. package/studio/web/pages/VideoPage/components/SoundTimeline/types.ts +43 -0
  491. package/studio/web/pages/VideoPage/components/SoundTimeline/useAutosave.ts +107 -0
  492. package/studio/web/pages/VideoPage/components/SoundTimeline/useEditorShortcuts.ts +168 -0
  493. package/studio/web/pages/VideoPage/components/SoundTimeline/useFitHeight.ts +40 -0
  494. package/studio/web/pages/VideoPage/components/SoundTimeline/useFollowPlayhead.ts +21 -0
  495. package/studio/web/pages/VideoPage/components/SoundTimeline/useListenMix.ts +43 -0
  496. package/studio/web/pages/VideoPage/components/SoundTimeline/usePlayback.ts +231 -0
  497. package/studio/web/pages/VideoPage/components/SoundTimeline/usePlayerWidth.ts +27 -0
  498. package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundData.ts +124 -0
  499. package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundTimeline.ts +511 -0
  500. package/studio/web/pages/VideoPage/components/StageDrawer/CompareVersions.tsx +111 -0
  501. package/studio/web/pages/VideoPage/components/StageDrawer/VersionCard.tsx +81 -0
  502. package/studio/web/pages/VideoPage/components/StageDrawer/VersionMedia.tsx +24 -0
  503. package/studio/web/pages/VideoPage/components/StageDrawer/VersionText.tsx +15 -0
  504. package/studio/web/pages/VideoPage/components/StageDrawer/index.tsx +109 -0
  505. package/studio/web/pages/VideoPage/components/StageProcess.tsx +126 -0
  506. package/studio/web/pages/VideoPage/components/StageResult.tsx +69 -0
  507. package/studio/web/pages/VideoPage/components/SwapReference.tsx +92 -0
  508. package/studio/web/pages/VideoPage/components/ThumbnailPanel.tsx +252 -0
  509. package/studio/web/pages/VideoPage/components/TimingPanel.tsx +89 -0
  510. package/studio/web/pages/VideoPage/components/VideoPlayer.tsx +18 -0
  511. package/studio/web/pages/VideoPage/components/VideoSkeleton.tsx +50 -0
  512. package/studio/web/pages/VideoPage/components/VideoVoiceInfo.tsx +33 -0
  513. package/studio/web/pages/VideoPage/components/VideoVoicePicker.tsx +165 -0
  514. package/studio/web/pages/VideoPage/components/VoicePauseBanner.tsx +56 -0
  515. package/studio/web/pages/VideoPage/components/WithLogo.tsx +10 -0
  516. package/studio/web/pages/VideoPage/index.tsx +295 -0
  517. package/studio/web/pages/VideosPage/components/PublishedOn.tsx +85 -0
  518. package/studio/web/pages/VideosPage/components/ServicesPanel.tsx +81 -0
  519. package/studio/web/pages/VideosPage/components/SessionsPanel.tsx +62 -0
  520. package/studio/web/pages/VideosPage/components/VideoListSkeleton.tsx +25 -0
  521. package/studio/web/pages/VideosPage/components/VideoRow.tsx +122 -0
  522. package/studio/web/pages/VideosPage/index.tsx +134 -0
  523. package/studio/web/pages/VoicePage/index.tsx +28 -0
  524. package/studio/web/router.tsx +201 -0
  525. package/studio/web/styles/base/elements.css +55 -0
  526. package/studio/web/styles/base/utilities.css +25 -0
  527. package/studio/web/styles/components/badge.css +33 -0
  528. package/studio/web/styles/components/claude-running.css +27 -0
  529. package/studio/web/styles/components/claude-session.css +212 -0
  530. package/studio/web/styles/components/claude-walking.css +92 -0
  531. package/studio/web/styles/components/clone-voice-save.css +136 -0
  532. package/studio/web/styles/components/clone-voice.css +379 -0
  533. package/studio/web/styles/components/controls.css +70 -0
  534. package/studio/web/styles/components/data-and-image.css +54 -0
  535. package/studio/web/styles/components/forms.css +132 -0
  536. package/studio/web/styles/components/human-banner.css +99 -0
  537. package/studio/web/styles/components/lightbox.css +50 -0
  538. package/studio/web/styles/components/markdown.css +68 -0
  539. package/studio/web/styles/components/modal.css +70 -0
  540. package/studio/web/styles/components/omni-cards.css +105 -0
  541. package/studio/web/styles/components/omni.css +859 -0
  542. package/studio/web/styles/components/omnichat.css +538 -0
  543. package/studio/web/styles/components/panel.css +115 -0
  544. package/studio/web/styles/components/platform-card.css +140 -0
  545. package/studio/web/styles/components/provider-card.css +132 -0
  546. package/studio/web/styles/components/queue.css +185 -0
  547. package/studio/web/styles/components/reference-actions.css +133 -0
  548. package/studio/web/styles/components/reference-picker.css +146 -0
  549. package/studio/web/styles/components/results-drawer.css +224 -0
  550. package/studio/web/styles/components/segmented.css +115 -0
  551. package/studio/web/styles/components/side-panel-tab.css +23 -0
  552. package/studio/web/styles/components/status-signals.css +66 -0
  553. package/studio/web/styles/components/stepper.css +208 -0
  554. package/studio/web/styles/components/table.css +19 -0
  555. package/studio/web/styles/components/tabs.css +24 -0
  556. package/studio/web/styles/components/ui/accordion.css +71 -0
  557. package/studio/web/styles/components/ui/chart.css +162 -0
  558. package/studio/web/styles/components/ui/checkbox.css +37 -0
  559. package/studio/web/styles/components/ui/date-picker.css +153 -0
  560. package/studio/web/styles/components/ui/select.css +110 -0
  561. package/studio/web/styles/components/ui/skeleton.css +25 -0
  562. package/studio/web/styles/components/ui/slider.css +81 -0
  563. package/studio/web/styles/components/ui/switch.css +41 -0
  564. package/studio/web/styles/components/ui/tooltip.css +24 -0
  565. package/studio/web/styles/components/voice-carousel.css +255 -0
  566. package/studio/web/styles/components/voice-editor.css +179 -0
  567. package/studio/web/styles/components/voice-grid.css +56 -0
  568. package/studio/web/styles/components/voice-profiles.css +192 -0
  569. package/studio/web/styles/design-system.md +136 -0
  570. package/studio/web/styles/index.css +116 -0
  571. package/studio/web/styles/layout/page.css +41 -0
  572. package/studio/web/styles/layout/sidebar-groups.css +39 -0
  573. package/studio/web/styles/layout/sidebar.css +111 -0
  574. package/studio/web/styles/pages/agent.css +26 -0
  575. package/studio/web/styles/pages/analytics/dashboard.css +614 -0
  576. package/studio/web/styles/pages/analytics/insights.css +48 -0
  577. package/studio/web/styles/pages/getting-started.css +166 -0
  578. package/studio/web/styles/pages/new-video/form.css +32 -0
  579. package/studio/web/styles/pages/not-found.css +59 -0
  580. package/studio/web/styles/pages/references.css +204 -0
  581. package/studio/web/styles/pages/schedule/calendar.css +541 -0
  582. package/studio/web/styles/pages/schedule/chip-states.css +60 -0
  583. package/studio/web/styles/pages/settings/models.css +72 -0
  584. package/studio/web/styles/pages/settings/shell.css +361 -0
  585. package/studio/web/styles/pages/settings/usage.css +63 -0
  586. package/studio/web/styles/pages/studio-offline.css +105 -0
  587. package/studio/web/styles/pages/video/caption-controls.css +194 -0
  588. package/studio/web/styles/pages/video/captions-grid.css +70 -0
  589. package/studio/web/styles/pages/video/captions.css +79 -0
  590. package/studio/web/styles/pages/video/editor-chat.css +103 -0
  591. package/studio/web/styles/pages/video/export.css +50 -0
  592. package/studio/web/styles/pages/video/frames.css +29 -0
  593. package/studio/web/styles/pages/video/fullscreen-stage.css +400 -0
  594. package/studio/web/styles/pages/video/instagram-carousel.css +309 -0
  595. package/studio/web/styles/pages/video/layout.css +285 -0
  596. package/studio/web/styles/pages/video/narration.css +138 -0
  597. package/studio/web/styles/pages/video/preview-overview.css +11 -0
  598. package/studio/web/styles/pages/video/processing-notice.css +34 -0
  599. package/studio/web/styles/pages/video/publish-when.css +76 -0
  600. package/studio/web/styles/pages/video/publish.css +97 -0
  601. package/studio/web/styles/pages/video/result.css +96 -0
  602. package/studio/web/styles/pages/video/scenes.css +167 -0
  603. package/studio/web/styles/pages/video/sound-timeline.css +672 -0
  604. package/studio/web/styles/pages/video/sound-toolbar.css +149 -0
  605. package/studio/web/styles/pages/video/take-player.css +100 -0
  606. package/studio/web/styles/pages/video/thumbnail.css +157 -0
  607. package/studio/web/styles/pages/video/timeline.css +117 -0
  608. package/studio/web/styles/pages/video/voice-picker.css +14 -0
  609. package/studio/web/styles/pages/video-list/active-row.css +27 -0
  610. package/studio/web/styles/pages/video-list/card-link.css +49 -0
  611. package/studio/web/styles/pages/video-list/list.css +86 -0
  612. package/studio/web/styles/theme/identity/details.css +204 -0
  613. package/studio/web/styles/theme/identity/shell.css +249 -0
  614. package/studio/web/styles/theme/identity/surfaces.css +183 -0
  615. package/studio/web/styles/theme/identity/titles-and-buttons.css +120 -0
  616. package/studio/web/styles/theme/no-borders/blocks.css +208 -0
  617. package/studio/web/styles/theme/no-borders/controls.css +256 -0
  618. package/studio/web/styles/tokens.css +245 -0
  619. package/studio/web/types/api.ts +2 -0
  620. package/studio/web/types/assets.d.ts +9 -0
  621. package/templates/brief.md +27 -0
  622. package/templates/captions.html +204 -0
  623. package/templates/carousel-composition.md +47 -0
  624. package/templates/carousel-script.md +60 -0
  625. package/templates/components/voiceavatar/README.md +87 -0
  626. package/templates/components/voiceavatar/demo.html +50 -0
  627. package/templates/components/voiceavatar/voiceavatar.js +415 -0
  628. package/templates/motion.md +80 -0
  629. package/templates/playbook.md +43 -0
  630. package/templates/reference.md +123 -0
  631. package/templates/scene.html +112 -0
  632. package/templates/scenes.example.json +96 -0
  633. package/templates/script.md +63 -0
  634. package/templates/slide.html +71 -0
  635. package/templates/wireframe.html +91 -0
  636. package/tools/analysis.mjs +300 -0
  637. package/tools/approval.mjs +442 -0
  638. package/tools/assemble.mjs +942 -0
  639. package/tools/build-motion.mjs +437 -0
  640. package/tools/captions.mjs +246 -0
  641. package/tools/carousel.mjs +558 -0
  642. package/tools/compose.mjs +307 -0
  643. package/tools/cuts.mjs +74 -0
  644. package/tools/editor.mjs +419 -0
  645. package/tools/epidemic-capture.js +54 -0
  646. package/tools/fanout.mjs +69 -0
  647. package/tools/history.mjs +70 -0
  648. package/tools/history.py +61 -0
  649. package/tools/job-queue.mjs +180 -0
  650. package/tools/job_queue.py +430 -0
  651. package/tools/lib/agents.mjs +412 -0
  652. package/tools/lib/channel.mjs +399 -0
  653. package/tools/lib/claude-settings.mjs +67 -0
  654. package/tools/lib/cli.mjs +9 -0
  655. package/tools/lib/fanout.mjs +234 -0
  656. package/tools/lib/http.mjs +94 -0
  657. package/tools/lib/index.js +14 -0
  658. package/tools/lib/mcp-server.mjs +69 -0
  659. package/tools/lib/metrics/metrics.mjs +204 -0
  660. package/tools/lib/narration.mjs +15 -0
  661. package/tools/lib/oauth-client.mjs +295 -0
  662. package/tools/lib/omnichat/config.mjs +139 -0
  663. package/tools/lib/omnichat/errors.mjs +42 -0
  664. package/tools/lib/omnichat/extract.mjs +82 -0
  665. package/tools/lib/omnichat/gate.mjs +151 -0
  666. package/tools/lib/omnichat/manual.mjs +166 -0
  667. package/tools/lib/omnichat/match.mjs +78 -0
  668. package/tools/lib/omnichat/pass.mjs +393 -0
  669. package/tools/lib/omnichat/record.mjs +208 -0
  670. package/tools/lib/omnichat/text.mjs +315 -0
  671. package/tools/lib/omnichat/webhook.mjs +166 -0
  672. package/tools/lib/pairing.mjs +99 -0
  673. package/tools/lib/paths.mjs +41 -0
  674. package/tools/lib/platforms/instagram.mjs +469 -0
  675. package/tools/lib/platforms/tiktok.mjs +343 -0
  676. package/tools/lib/platforms/youtube.mjs +228 -0
  677. package/tools/lib/publish/publish.mjs +244 -0
  678. package/tools/lib/schedule/schedule.mjs +191 -0
  679. package/tools/lib/service.mjs +134 -0
  680. package/tools/lib/skill-client.mjs +126 -0
  681. package/tools/lib/skills-release.mjs +196 -0
  682. package/tools/lib/testing.mjs +52 -0
  683. package/tools/lib/up.mjs +187 -0
  684. package/tools/metrics.mjs +415 -0
  685. package/tools/models.mjs +134 -0
  686. package/tools/narrate.mjs +257 -0
  687. package/tools/omni.mjs +298 -0
  688. package/tools/omni_paths.py +12 -0
  689. package/tools/omnichat.mjs +1577 -0
  690. package/tools/omnivideo.py +1122 -0
  691. package/tools/package.mjs +130 -0
  692. package/tools/platforms.mjs +236 -0
  693. package/tools/premiere.mjs +533 -0
  694. package/tools/publish.mjs +1496 -0
  695. package/tools/reference.mjs +1408 -0
  696. package/tools/render.mjs +426 -0
  697. package/tools/runner.mjs +156 -0
  698. package/tools/setup.mjs +466 -0
  699. package/tools/skills-release.mjs +93 -0
  700. package/tools/sound.mjs +1323 -0
  701. package/tools/soundtrack.mjs +217 -0
  702. package/tools/thumbnail.mjs +271 -0
  703. package/tools/timing.mjs +108 -0
  704. package/tools/usage.py +628 -0
  705. package/tools/voice-envelope.mjs +185 -0
  706. package/tools/voice.py +448 -0
  707. package/tools/voice_sample.py +75 -0
  708. package/tools/wireframe.mjs +230 -0
@@ -0,0 +1,217 @@
1
+ /* ============================================================================
2
+ Clips: where each piece of animation (a scene's MP4) and of voice (a line's
3
+ WAV) shows up in the video.
4
+
5
+ The voice owns time: the scene starts at `scene.start` and lasts
6
+ `scene.duration`; the line sits at `scene.start + line.start`. The studio
7
+ editor lets you change that without redoing anything, through clips:
8
+
9
+ scene.clips [{ from, to, offset, speed? }] pieces of the scene MP4
10
+ line.clips [{ from, to, offset, speed? }] pieces of the line WAV
11
+
12
+ `from`/`to` are seconds inside the file; `offset` is how far the piece moves
13
+ from its own place (negative = earlier). `speed` (0.5 to 2, default 1) plays
14
+ the piece faster: it lasts (to − from) / speed in the video, starting at the
15
+ same place, and what came after stays put (the gap holds the last frame, or
16
+ is silent). The voice keeps its pitch (atempo). The razor splits a clip in two;
17
+ deleting a clip removes the piece; dragging changes `offset`. Without
18
+ `clips`, the whole file is one clip (for a scene, with the old
19
+ `video_offset`, if any).
20
+
21
+ Beat and transition effects move with the animation piece they fall in;
22
+ captions and ducking move with the voice piece.
23
+
24
+ Image on a single track, like an editor: where two pieces overlap, the one
25
+ that started last shows; in a gap, the last frame of the piece that ended
26
+ before holds (or the first frame of the first one, before everything).
27
+
28
+ assemble.mjs and sound.mjs use this; so does the studio screen. Times in
29
+ scenes.json seconds (without the range cuts).
30
+ ============================================================================ */
31
+
32
+ const EPS = 1e-6;
33
+ /* `video_em` = legacy name of `video_offset` */
34
+ export const videoOffset = scene => Number(scene?.video_offset ?? scene?.video_em) || 0;
35
+
36
+ export const CLIP_SPEED = [0.5, 2];
37
+ /* a clip's speed, inside the range (1 when absent) */
38
+ export const clipSpeed = c => {
39
+ const v = Number(c?.speed);
40
+ return Number.isFinite(v) && v > 0 ? Math.min(CLIP_SPEED[1], Math.max(CLIP_SPEED[0], v)) : 1;
41
+ };
42
+ /* how long the piece lasts in the video */
43
+ export const clipLength = c => (c.to - c.from) / clipSpeed(c);
44
+ /* `speed` only when it isn't 1, so an untouched clip stays { from, to, offset } */
45
+ const normal = c => {
46
+ const v = clipSpeed(c);
47
+ return { from: +c.from, to: +c.to, offset: Number(c.offset) || 0, ...(v !== 1 ? { speed: v } : {}) };
48
+ };
49
+ export const lineDuration = l => l.dur ?? l.end - l.start;
50
+
51
+ export function videoClips(scene) {
52
+ return Array.isArray(scene.clips)
53
+ ? scene.clips.map(normal)
54
+ : [{ from: 0, to: scene.duration, offset: videoOffset(scene) }];
55
+ }
56
+ export function lineClips(line) {
57
+ return Array.isArray(line.clips) ? line.clips.map(normal) : [{ from: 0, to: lineDuration(line), offset: 0 }];
58
+ }
59
+
60
+ /* the arrangement was edited: some clip moved, cut or deleted */
61
+ const isDefault = (clips, dur) =>
62
+ clips.length === 1 &&
63
+ Math.abs(clips[0].from) < EPS &&
64
+ Math.abs(clips[0].to - dur) < 1e-3 &&
65
+ !clips[0].offset &&
66
+ clipSpeed(clips[0]) === 1;
67
+ export const videoEdited = scenes => scenes.some(c => !isDefault(videoClips(c), c.duration));
68
+ export const voiceEdited = scenes =>
69
+ scenes.some(c => (c.lines ?? []).some(l => !isDefault(lineClips(l), lineDuration(l))));
70
+
71
+ /* every animation piece: { i (scene), k (clip), from, to, offset, speed, start and end (in the video) } */
72
+ export function videoPieces(scenes) {
73
+ return scenes.flatMap((c, i) =>
74
+ videoClips(c).map((cl, k) => ({
75
+ i,
76
+ k,
77
+ from: cl.from,
78
+ to: cl.to,
79
+ offset: cl.offset,
80
+ speed: clipSpeed(cl),
81
+ start: c.start + cl.from + cl.offset,
82
+ end: c.start + cl.from + cl.offset + clipLength(cl),
83
+ })),
84
+ );
85
+ }
86
+ /* every voice piece: { scene, line, k, from, to, offset, speed, start } */
87
+ export function voicePieces(scenes) {
88
+ return scenes.flatMap(c =>
89
+ (c.lines ?? []).flatMap(l =>
90
+ lineClips(l).map((cl, k) => ({
91
+ scene: c,
92
+ line: l,
93
+ k,
94
+ from: cl.from,
95
+ to: cl.to,
96
+ offset: cl.offset,
97
+ speed: clipSpeed(cl),
98
+ start: c.start + l.start + cl.from + cl.offset,
99
+ })),
100
+ ),
101
+ );
102
+ }
103
+
104
+ /* the instant, in the video, of a `local` point of the scene's animation (null: fell in a deleted piece) */
105
+ export function animationInstant(scene, local) {
106
+ const cl = videoClips(scene).find(x => local >= x.from - EPS && local < x.to + EPS);
107
+ return cl ? scene.start + cl.from + cl.offset + (local - cl.from) / clipSpeed(cl) : null;
108
+ }
109
+ /* the start of the scene's animation in the video (the scene change): the first piece's */
110
+ export function animationStart(scene) {
111
+ const cl = videoClips(scene).sort((a, b) => a.from - b.from)[0];
112
+ return cl ? scene.start + cl.from + cl.offset : null;
113
+ }
114
+ /* a span [a, b] inside the line, in the video: one piece per clip that holds it (the rest was deleted) */
115
+ export function voiceSpans(scene, line, a, b) {
116
+ const base = scene.start + line.start;
117
+ return lineClips(line).flatMap(cl => {
118
+ const from = Math.max(a, cl.from),
119
+ to = Math.min(b, cl.to);
120
+ const at = x => base + cl.from + cl.offset + (x - cl.from) / clipSpeed(cl);
121
+ return to > from + EPS ? [[at(from), at(to)]] : [];
122
+ });
123
+ }
124
+
125
+ /* what shows at instant t: { i (scene), local (second inside the MP4), frozen, speed (how fast the
126
+ MP4 runs there) } */
127
+ export function frameAt(scenes, t) {
128
+ const pieces = videoPieces(scenes);
129
+ if (!pieces.length) return { i: 0, local: 0, frozen: true, speed: 1 };
130
+ let covering = null;
131
+ for (const p of pieces) if (p.start <= t && t < p.end && (!covering || p.start >= covering.start)) covering = p;
132
+ if (covering)
133
+ return {
134
+ i: covering.i,
135
+ local: covering.from + (t - covering.start) * covering.speed,
136
+ frozen: false,
137
+ speed: covering.speed,
138
+ };
139
+ /* gap: the last frame of the piece that ended last before t */
140
+ let ended = null;
141
+ for (const p of pieces) if (p.end <= t && (!ended || p.end >= ended.end)) ended = p;
142
+ if (ended) return { i: ended.i, local: ended.to - 1e-3, frozen: true, speed: 1 };
143
+ /* before all of them: the first frame of the one that starts first */
144
+ const first = pieces.reduce((m, p) => (p.start < m.start ? p : m));
145
+ return { i: first.i, local: first.from, frozen: true, speed: 1 };
146
+ }
147
+
148
+ /* the whole track in frames: [{ i, from, frames, since, frozen, speed }], `from` and `since`
149
+ in frames (in the video and inside the scene MP4); frozen repeats frame `since`; a running
150
+ segment takes frames × speed frames of the MP4 */
151
+ export function videoTrack(scenes, total, fps) {
152
+ const q = s => Math.round(s * fps);
153
+ const end = q(total);
154
+ const marks = new Set([0, end]);
155
+ for (const p of videoPieces(scenes)) {
156
+ for (const m of [q(p.start), q(p.end)]) if (m > 0 && m < end) marks.add(m);
157
+ }
158
+ const points = [...marks].sort((a, b) => a - b);
159
+ const segs = [];
160
+ for (let k = 0; k < points.length - 1; k++) {
161
+ const a = points[k],
162
+ b = points[k + 1];
163
+ const v = frameAt(scenes, (a + 0.5) / fps);
164
+ const nq = q(scenes[v.i].duration);
165
+ /* the MP4 frame at the start of the segment */
166
+ const since = Math.max(
167
+ 0,
168
+ Math.min(
169
+ nq - 1,
170
+ v.frozen ? Math.floor(v.local * fps + EPS) : Math.round((v.local - (0.5 * v.speed) / fps) * fps),
171
+ ),
172
+ );
173
+ const last = segs.at(-1);
174
+ /* join with the previous one when it is the same scene running at the same speed, or the same frozen frame */
175
+ if (
176
+ last &&
177
+ last.i === v.i &&
178
+ last.frozen === v.frozen &&
179
+ last.speed === v.speed &&
180
+ (v.frozen ? last.since === since : Math.abs(last.since + last.frames * last.speed - since) < 1 + EPS)
181
+ )
182
+ last.frames += b - a;
183
+ else segs.push({ i: v.i, from: a, frames: b - a, since, frozen: v.frozen, speed: v.speed });
184
+ }
185
+ return segs;
186
+ }
187
+
188
+ /* --- validate what the screen sends --------------------------------------------
189
+ clips sorted, inside the file, not overlapping in the file; `step` snaps
190
+ (1/fps for animation, 1 ms for voice); `offset` from -30 to 30 s */
191
+ export function sanitizeClips(clips, dur, step) {
192
+ if (!Array.isArray(clips)) throw new Error("clips: tem de ser uma lista");
193
+ const a = s => Math.round(s / step) * step;
194
+ const list = clips
195
+ .map(c => {
196
+ const from = a(Math.max(0, Math.min(dur, Number(c?.from)))),
197
+ to = a(Math.max(0, Math.min(dur, Number(c?.to))));
198
+ const offset = Number(c?.offset ?? 0);
199
+ if (![from, to, offset].every(Number.isFinite)) throw new Error(`clip inválido: ${JSON.stringify(c)}`);
200
+ if (Math.abs(offset) > 30) throw new Error(`clip: offset de -30 a 30 segundos; veio ${offset}`);
201
+ const speed = c?.speed == null ? 1 : Number(c.speed);
202
+ if (!(speed >= CLIP_SPEED[0] && speed <= CLIP_SPEED[1]))
203
+ throw new Error(`clip: velocidade de ${CLIP_SPEED[0] * 100}% a ${CLIP_SPEED[1] * 100}%; veio ${c?.speed}`);
204
+ return { from, to, offset: a(offset), speed: Math.round(speed * 100) / 100 };
205
+ })
206
+ .filter(c => c.to - c.from >= step - EPS)
207
+ .sort((x, y) => x.from - y.from);
208
+ for (let k = 1; k < list.length; k++) if (list[k].from < list[k - 1].to - EPS) list[k].from = list[k - 1].to;
209
+ return list
210
+ .filter(c => c.to - c.from >= step - EPS)
211
+ .map(c => ({
212
+ from: +c.from.toFixed(4),
213
+ to: +c.to.toFixed(4),
214
+ offset: +c.offset.toFixed(4),
215
+ ...(c.speed !== 1 ? { speed: c.speed } : {}),
216
+ }));
217
+ }
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env node
2
+ /* ============================================================================
3
+ Thumbnail of a video in HTML + SVG + CSS, on request. Four slots:
4
+ v1, v2 and v3 are three drawn ideas; v4 follows the user's instruction.
5
+
6
+ node tools/thumbnail.mjs videos/<slug> size aspect and pixels of this video's thumbnail
7
+ node tools/thumbnail.mjs videos/<slug> draw three different ideas for v1, v2 and v3
8
+ node tools/thumbnail.mjs videos/<slug> render [vN,…] thumbnail/vN.html → vN.png (default: the ones that exist)
9
+ node tools/thumbnail.mjs videos/<slug> snapshot v2 copies v2.html and v2.png to history/ before changing it
10
+ node tools/thumbnail.mjs videos/<slug> choose v2 the selected one becomes thumbnail/thumbnail.png, the cover
11
+ that publish.mjs sends (choose none removes it)
12
+ node tools/thumbnail.mjs videos/<slug> status thumbnail/thumbnail.json
13
+
14
+ Claude creates the versions (skill av-thumbnail), in the video's style;
15
+ this tool is the mechanical part: the right size for the video's aspect,
16
+ the idea draw, the PNG of each HTML and the record in
17
+ thumbnail/thumbnail.json. Rendering some doesn't delete the others: the
18
+ random ones and the manual one are requested and redone separately.
19
+
20
+ Size from the aspect in scenes.json:
21
+ 16:9 → 1280×720 (what YouTube asks for)
22
+ 9:16 → 1080×1920 (Shorts, Reels, TikTok)
23
+ other → the video's own format
24
+
25
+ Each HTML is a still, self-contained image (no JS needed, no external
26
+ files): the tool adds the ready signal that render.mjs waits for, so the
27
+ source only has to load. `<title>` becomes the version name and
28
+ `<meta name="description">` its idea, in thumbnail.json and in the UI.
29
+
30
+ thumbnail/request.json belongs to the UI (studio/api/services/thumbnail.ts):
31
+ { "random": { "at": "…" } | null,
32
+ "manual": { "at": "…", "instruction": "…" } | null,
33
+ "changes": { "v2": { "at": "…", "instruction": "…" } } }
34
+ The v4 instruction and the change requested for each version go from it into thumbnail.json.
35
+
36
+ Nothing here is sent to the platforms: publish.mjs does not upload the cover.
37
+ ============================================================================ */
38
+
39
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync, renameSync, rmSync } from "node:fs";
40
+ import { spawnSync } from "node:child_process";
41
+ import { join, resolve } from "node:path";
42
+ import { keepPrevious } from "./history.mjs";
43
+ import { CODE_ROOT, DATA_ROOT as ROOT } from "./lib/paths.mjs";
44
+
45
+ const RENDER = join(CODE_ROOT, "tools", "render.mjs");
46
+ const RANDOM = ["v1", "v2", "v3"];
47
+ const MANUAL = "v4";
48
+ const IDS = [...RANDOM, MANUAL];
49
+
50
+ /* where the three random ones start from; the draw changes on every request (PT: shown to Claude) */
51
+ const IDEAS = [
52
+ ["tipografia", "uma palavra ou número enorme, o resto some"],
53
+ ["ilustração", "um objeto ou diagrama em SVG como herói, texto mínimo"],
54
+ ["contraste", "antes/depois, certo/errado, duas metades"],
55
+ ["pergunta", "o gancho do roteiro como pergunta curta, com um símbolo"],
56
+ ["dado", "o número do vídeo como painel: placar, medidor, contador"],
57
+ ["cena", 'o herói da cena "wow" redesenhado grande, recortado de perto'],
58
+ ["metáfora", "um objeto do dia a dia que explica a tese sem palavras"],
59
+ ];
60
+
61
+ function fail(msg) {
62
+ console.error(`erro: ${msg}`);
63
+ process.exit(1);
64
+ }
65
+ const readJson = file => {
66
+ try {
67
+ return JSON.parse(readFileSync(file, "utf8"));
68
+ } catch {
69
+ return null;
70
+ }
71
+ };
72
+ function writeJson(file, data) {
73
+ const tmp = `${file}.${process.pid}.tmp`;
74
+ writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n");
75
+ renameSync(tmp, file);
76
+ }
77
+
78
+ const [target, cmd = "status", ...rest] = process.argv.slice(2);
79
+ if (!target) fail("uso: thumbnail.mjs videos/<slug> size|draw|render [vN,…]|snapshot <vN>|choose <vN>|status");
80
+ const VIDEO_DIR = resolve(target);
81
+ const scenes = readJson(join(VIDEO_DIR, "scenes.json"));
82
+ if (!scenes) fail(`${target} não tem scenes.json`);
83
+ const DIR = join(VIDEO_DIR, "thumbnail");
84
+ const JSON_FILE = join(DIR, "thumbnail.json");
85
+
86
+ function size() {
87
+ const f = scenes.format;
88
+ if (!f) fail("o scenes.json não tem format");
89
+ const aspect = f.aspect ?? `${f.w}:${f.h}`;
90
+ if (aspect === "16:9") return { aspect, w: 1280, h: 720 };
91
+ if (aspect === "9:16") return { aspect, w: 1080, h: 1920 };
92
+ return { aspect, w: f.w, h: f.h };
93
+ }
94
+
95
+ function draw() {
96
+ const bag = [...IDEAS];
97
+ for (let i = bag.length - 1; i > 0; i--) {
98
+ const j = Math.floor(Math.random() * (i + 1));
99
+ [bag[i], bag[j]] = [bag[j], bag[i]];
100
+ }
101
+ bag.slice(0, 3).forEach(([name, how], i) => console.log(`${RANDOM[i]}: ${name} (${how})`));
102
+ }
103
+
104
+ /* request.json from before this split ({ at, request }, older { em, pedido }) was the three's */
105
+ function requests() {
106
+ const p = readJson(join(DIR, "request.json"));
107
+ if (p && ("at" in p || "em" in p)) return { random: { at: p.at ?? p.em }, manual: null, changes: {} };
108
+ return { random: p?.random ?? null, manual: p?.manual ?? null, changes: p?.changes ?? {} };
109
+ }
110
+
111
+ const attribute = (html, re) => {
112
+ const m = re.exec(html);
113
+ return m ? m[1].replace(/\s+/g, " ").trim() : null;
114
+ };
115
+
116
+ /* The ready signal render waits for, after measuring the content box:
117
+ the union of the visible leaves (an <svg> counts whole), without what covers
118
+ the whole frame (background, texture). It becomes a [data-continues] that
119
+ render.mjs --measure returns. */
120
+ const READY = `<script>document.fonts.ready.then(()=>{
121
+ const W=innerWidth,H=innerHeight;let x0=W,y0=H,x1=0,y1=0;
122
+ for(const e of document.body.querySelectorAll("*")){
123
+ const tag=e.tagName.toLowerCase();
124
+ if(["script","style","link","meta"].includes(tag))continue;
125
+ if(tag!=="svg"&&e.closest("svg"))continue;
126
+ if(tag!=="svg"&&e.children.length)continue;
127
+ const cs=getComputedStyle(e);if(cs.visibility==="hidden"||+cs.opacity===0)continue;
128
+ const r=e.getBoundingClientRect();if(r.width<1||r.height<1)continue;
129
+ if(r.width>=.9*W&&r.height>=.9*H)continue;
130
+ x0=Math.min(x0,Math.max(0,r.left));y0=Math.min(y0,Math.max(0,r.top));
131
+ x1=Math.max(x1,Math.min(W,r.right));y1=Math.max(y1,Math.min(H,r.bottom));
132
+ }
133
+ if(x1>x0&&y1>y0){const m=document.createElement("div");m.dataset.continues="content";
134
+ Object.assign(m.style,{position:"fixed",left:x0+"px",top:y0+"px",width:(x1-x0)+"px",height:(y1-y0)+"px",pointerEvents:"none"});
135
+ document.body.appendChild(m);}
136
+ window.__ready=true;
137
+ })</script>`;
138
+
139
+ /* 9:16 has the content centred in x and y (the profile grid crops through the middle) */
140
+ const MEASUREMENTS = "MEASUREMENTS ";
141
+ function offCenter(stdout, t) {
142
+ if (t.h <= t.w) return null;
143
+ const line = (stdout ?? "").split("\n").find(l => l.startsWith(MEASUREMENTS));
144
+ const box = line ? JSON.parse(line.slice(MEASUREMENTS.length)).find(m => m.name === "content") : null;
145
+ if (!box) return null;
146
+ const dx = (box.x + box.w / 2) / t.w - 0.5,
147
+ dy = (box.y + box.h / 2) / t.h - 0.5;
148
+ const pct = n => `${Math.round(Math.abs(n) * 100)}%`;
149
+ const errors = [
150
+ Math.abs(dx) > 0.05 ? `${pct(dx)} para a ${dx < 0 ? "esquerda" : "direita"}` : null,
151
+ Math.abs(dy) > 0.06 ? `${pct(dy)} para ${dy < 0 ? "cima" : "baixo"}` : null,
152
+ ].filter(Boolean);
153
+ return errors.length ? `conteúdo fora do centro em 9:16 (${errors.join(", ")}): centre em x e y` : null;
154
+ }
155
+
156
+ function render(list) {
157
+ const t = size();
158
+ const requested = list ? list.split(",").filter(Boolean) : IDS;
159
+ const invalid = requested.filter(id => !IDS.includes(id));
160
+ if (invalid.length) fail(`versão inválida: ${invalid.join(", ")} (use ${IDS.join(", ")})`);
161
+ const present = requested.filter(id => existsSync(join(DIR, `${id}.html`)));
162
+ if (!present.length) fail(`escreva thumbnail/${requested.join(".html, ")}.html antes (${t.w}×${t.h}, ${t.aspect})`);
163
+ const missing = requested.filter(id => list && !present.includes(id));
164
+ if (missing.length) console.warn(`aviso: sem HTML para ${missing.join(", ")}`);
165
+
166
+ const previous = readJson(JSON_FILE);
167
+ /* the format changed (video redone in another aspect): the old versions are no use */
168
+ const same = previous?.format?.w === t.w && previous?.format?.h === t.h;
169
+ const versions = new Map((same ? (previous.versions ?? []) : []).map(v => [v.id, v]));
170
+ const { manual, changes } = requests();
171
+ for (const id of present) {
172
+ const html = readFileSync(join(DIR, `${id}.html`), "utf8");
173
+ const png = join(DIR, `${id}.png`);
174
+ /* the previous image goes to history/thumbnail/<vN>/ (the HTML was already rewritten) */
175
+ const kept = keepPrevious(VIDEO_DIR, "thumbnail", id, [`thumbnail/${id}.png`]);
176
+
177
+ /* side copy, with the ready signal render waits for, so the font
178
+ loads without the scene having to remember it */
179
+ const temp = join(DIR, `.render-${id}.html`);
180
+ writeFileSync(temp, /<\/body>/i.test(html) ? html.replace(/<\/body>/i, `${READY}</body>`) : html + READY);
181
+ const r = spawnSync(
182
+ "node",
183
+ [RENDER, temp, "--still", png, "--at", "0", "--w", String(t.w), "--h", String(t.h), "--measure"],
184
+ { encoding: "utf8" },
185
+ );
186
+ rmSync(temp, { force: true });
187
+ if (r.status !== 0)
188
+ fail(`${id}: o render saiu com ${r.status}\n${(r.stderr || r.stdout || "").trim().slice(-600)}`);
189
+ kept.close();
190
+ const off = offCenter(r.stdout, t);
191
+ if (off) console.warn(`aviso: ${id}: ${off}`);
192
+
193
+ versions.set(id, {
194
+ id,
195
+ name: attribute(html, /<title[^>]*>([^<]*)<\/title>/i) ?? id,
196
+ /* `descricao` is the legacy meta name */
197
+ description: attribute(html, /<meta\s+name=["'](?:description|descricao)["']\s+content=["']([^"']*)["']/i),
198
+ html: `thumbnail/${id}.html`,
199
+ png: `thumbnail/${id}.png`,
200
+ generated_at: new Date().toISOString(),
201
+ ...(id === MANUAL ? { instruction: manual?.instruction ?? null } : {}),
202
+ /* what was asked to change in this version (the UI shows it); gone when it is redone from scratch */
203
+ ...(changes[id] ? { change: changes[id] } : {}),
204
+ });
205
+ console.log(`${id}: thumbnail/${id}.png (${t.w}×${t.h})`);
206
+ }
207
+
208
+ /* the selected one only drops if its image changed: it goes to history */
209
+ let selected = same ? (previous?.selected ?? null) : null;
210
+ const final = join(DIR, "thumbnail.png");
211
+ if (!selected || present.includes(selected)) {
212
+ if (existsSync(final)) {
213
+ keepPrevious(VIDEO_DIR, "thumbnail", "final", ["thumbnail/thumbnail.png"]).close();
214
+ rmSync(final, { force: true });
215
+ }
216
+ selected = null;
217
+ }
218
+ writeJson(JSON_FILE, {
219
+ format: t,
220
+ generated_at: new Date().toISOString(),
221
+ versions: IDS.map(id => versions.get(id)).filter(Boolean),
222
+ selected,
223
+ ...(selected && previous?.selected_at ? { selected_at: previous.selected_at } : {}),
224
+ });
225
+ console.log(
226
+ `${present.length} versão(ões) renderizada(s) · escolha com: node tools/thumbnail.mjs ${target} choose <${IDS.join("|")}>`,
227
+ );
228
+ }
229
+
230
+ /* before changing a version: the current HTML and PNG go to history/thumbnail/<vN>/ */
231
+ function snapshot(id) {
232
+ if (!IDS.includes(id)) fail(`versão inválida: ${id || "(vazia)"} (use ${IDS.join(", ")})`);
233
+ if (!existsSync(join(DIR, `${id}.html`))) fail(`thumbnail/${id}.html não existe`);
234
+ keepPrevious(VIDEO_DIR, "thumbnail", id, [`thumbnail/${id}.html`, `thumbnail/${id}.png`]).close({ keep: true });
235
+ console.log(`${id}: versão de agora guardada em history/thumbnail/${id}/`);
236
+ }
237
+
238
+ function choose(id) {
239
+ const data = readJson(JSON_FILE);
240
+ if (id === "none") {
241
+ const final = join(DIR, "thumbnail.png");
242
+ if (existsSync(final)) {
243
+ keepPrevious(VIDEO_DIR, "thumbnail", "final", ["thumbnail/thumbnail.png"]).close();
244
+ rmSync(final, { force: true });
245
+ }
246
+ if (data) {
247
+ delete data.selected_at;
248
+ writeJson(JSON_FILE, { ...data, selected: null });
249
+ }
250
+ console.log("nenhuma thumbnail selecionada: a publicação usa um quadro do vídeo");
251
+ return;
252
+ }
253
+ if (!IDS.includes(id)) fail(`versão inválida: ${id || "(vazia)"} (use ${IDS.join(", ")} ou none)`);
254
+ if (!data?.versions?.some(v => v.id === id)) fail(`a versão ${id} ainda não foi renderizada`);
255
+ const source = join(DIR, `${id}.png`);
256
+ if (!existsSync(source)) fail(`thumbnail/${id}.png não existe: renderize de novo`);
257
+ const final = join(DIR, "thumbnail.png");
258
+ keepPrevious(VIDEO_DIR, "thumbnail", "final", ["thumbnail/thumbnail.png"]).close();
259
+ copyFileSync(source, final);
260
+ writeJson(JSON_FILE, { ...data, selected: id, selected_at: new Date().toISOString() });
261
+ console.log(`${id} é a thumbnail do vídeo: thumbnail/thumbnail.png`);
262
+ }
263
+
264
+ mkdirSync(DIR, { recursive: true });
265
+ if (cmd === "size") console.log(JSON.stringify(size()));
266
+ else if (cmd === "draw") draw();
267
+ else if (cmd === "render") render(rest[0]);
268
+ else if (cmd === "snapshot") snapshot(rest[0]);
269
+ else if (cmd === "choose") choose(rest[0]);
270
+ else if (cmd === "status") console.log(JSON.stringify(readJson(JSON_FILE) ?? { versions: [], size: size() }, null, 2));
271
+ else fail(`comando desconhecido: ${cmd}`);
@@ -0,0 +1,108 @@
1
+ /* ============================================================================
2
+ Time references in scenes.json → seconds inside the scene.
3
+
4
+ Each video's build.mjs uses this to place every motion beat exactly where
5
+ the voice says the word:
6
+
7
+ import { loadVideo, beatTime } from "../../../tools/timing.mjs";
8
+ const video = loadVideo(new URL("..", import.meta.url).pathname);
9
+ for (const scene of video.scenes) {
10
+ for (const beat of scene.beats) {
11
+ const t = beatTime(scene, beat); // seconds in the scene
12
+ }
13
+ }
14
+
15
+ Accepted forms:
16
+ 2.4 number, passed through
17
+ "c01f2" line start
18
+ "c01f2:end" line end (legacy ":fim" also accepted)
19
+ "c01f2:milissegundos" start of the word's first occurrence
20
+ "c01f2:token#2" second occurrence
21
+ "c01f2:end-0.3" any form above with an offset (+/-)
22
+
23
+ Before `omnivideo.py timing` runs, the line has no audio yet: its time is
24
+ estimated from the config's average pace and `video.provisional` is true.
25
+ Good for stills and contact sheets; the final render requires real timing.
26
+ ============================================================================ */
27
+
28
+ import { readFileSync } from "node:fs";
29
+ import { join } from "node:path";
30
+ import { DATA_ROOT as ROOT } from "./lib/paths.mjs";
31
+
32
+ const CFG = JSON.parse(readFileSync(join(ROOT, "omnivideo.config.json"), "utf8"));
33
+
34
+ const norm = w =>
35
+ w
36
+ .toLowerCase()
37
+ .normalize("NFC")
38
+ .replace(/[^\p{L}\p{N}]/gu, "");
39
+
40
+ export function loadVideo(videoDir) {
41
+ const video = JSON.parse(readFileSync(join(videoDir, "scenes.json"), "utf8"));
42
+ const { lead_in, pause_between_lines, final_hold, words_per_second } = CFG.timing;
43
+ video.provisional = false;
44
+ let clock = 0;
45
+ for (const scene of video.scenes) {
46
+ let cursor = scene.lead_in ?? lead_in;
47
+ for (const line of scene.lines ?? []) {
48
+ if (line.start == null) {
49
+ /* with audio (the voice generated before `timing`), the duration is real */
50
+ video.provisional = true;
51
+ const n = line.text.split(/\s+/).length;
52
+ line.start = cursor;
53
+ line.end = cursor + (line.dur ?? n / words_per_second);
54
+ }
55
+ cursor = line.end + pause_between_lines;
56
+ }
57
+ /* without `timing` run (including no lines yet) duration and start are
58
+ estimated too */
59
+ if (scene.duration == null) {
60
+ video.provisional = true;
61
+ const end = scene.lines?.length ? scene.lines.at(-1).end : 0;
62
+ scene.duration = Math.max(end + (scene.hold ?? final_hold), scene.min_duration ?? 0);
63
+ }
64
+ if (scene.start == null) {
65
+ video.provisional = true;
66
+ scene.start = clock;
67
+ }
68
+ clock = scene.start + scene.duration;
69
+ }
70
+ video.total_duration ??= clock;
71
+ return video;
72
+ }
73
+
74
+ /* A beat is born in composition with only `t` (provisional
75
+ seconds, for the still and the contact sheet). Narration ties each beat to a
76
+ line or word in `enters_with`, and from then on that rules. */
77
+ export function beatTime(scene, beat) {
78
+ if (beat.enters_with != null) return resolveTime(scene, beat.enters_with);
79
+ if (beat.t != null) return beat.t;
80
+ throw new Error(`${scene.id}: beat ${beat.element} sem enters_with nem t`);
81
+ }
82
+
83
+ export function resolveTime(scene, ref) {
84
+ if (typeof ref === "number") return ref;
85
+ const m = /^([^:+-]+)(?::([^+\-#]+)(?:#(\d+))?)?([+-]\d+(?:\.\d+)?)?$/.exec(String(ref).trim());
86
+ if (!m) throw new Error(`referência de tempo inválida: ${ref}`);
87
+ const [, lineId, target, occurrence, offset] = m;
88
+ const line = (scene.lines ?? []).find(l => l.id === lineId);
89
+ if (!line) throw new Error(`${scene.id}: fala ${lineId} não existe`);
90
+
91
+ let t;
92
+ if (!target) t = line.start;
93
+ else if (target === "end" || target === "fim")
94
+ t = line.end; // ":fim" = legacy alias
95
+ else if (line.words) {
96
+ const hits = line.words.filter(p => norm(p.w) === norm(target));
97
+ const hit = hits[(+occurrence || 1) - 1];
98
+ if (!hit) throw new Error(`${scene.id}: “${target}” não aparece na fala ${lineId}`);
99
+ t = line.start + hit.s;
100
+ } else {
101
+ /* no word timing yet: proportional to the position in the text */
102
+ const words = line.text.split(/\s+/);
103
+ const idx = words.findIndex(p => norm(p) === norm(target));
104
+ if (idx < 0) throw new Error(`${scene.id}: “${target}” não aparece na fala ${lineId}`);
105
+ t = line.start + ((line.end - line.start) * idx) / words.length;
106
+ }
107
+ return Math.round((t + (offset ? +offset : 0)) * 1000) / 1000;
108
+ }