auricui-skill 0.0.0-stage → 0.1.2
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.
- package/LICENSE +21 -0
- package/README.md +29 -2
- package/bin/install.mjs +66 -0
- package/package.json +29 -4
- package/skill/SKILL.md +64 -0
- package/skill/agents/openai.yaml +4 -0
- package/skill/assets/examples/ApprovalCard.tsx +8 -0
- package/skill/assets/examples/BasicApp.tsx +38 -0
- package/skill/assets/examples/BriefingCard.tsx +8 -0
- package/skill/assets/examples/ChatControls.tsx +49 -0
- package/skill/assets/examples/ChatPanel.tsx +73 -0
- package/skill/assets/examples/Checkbox.tsx +13 -0
- package/skill/assets/examples/Combobox.tsx +14 -0
- package/skill/assets/examples/Drawer.tsx +10 -0
- package/skill/assets/examples/MemoryCard.tsx +9 -0
- package/skill/assets/examples/MessageList.tsx +8 -0
- package/skill/assets/examples/Theme.tsx +14 -0
- package/skill/assets/examples/ToolCall.tsx +8 -0
- package/skill/assets/examples/Tree.tsx +15 -0
- package/skill/assets/examples/examples.css +11 -0
- package/skill/assets/library/auricui-0.1.0-preview.1.tgz +0 -0
- package/skill/assets/library/auricui-0.1.0-preview.3.tgz +0 -0
- package/skill/assets/library/auricui-0.1.0-preview.4.tgz +0 -0
- package/skill/assets/upstream/LICENSE +21 -0
- package/skill/assets/upstream/apps/desktop/chat/src/App.tsx +1099 -0
- package/skill/assets/upstream/apps/desktop/chat/src/app-bridge.ts +154 -0
- package/skill/assets/upstream/apps/desktop/chat/src/chat.css +1812 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/AddSkill.tsx +223 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/AgentActivityRow.tsx +716 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/AgentIcons.tsx +46 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentCard.tsx +34 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentPreview.tsx +331 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/BalanceNotice.tsx +22 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/BrandLogo.tsx +35 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/ComposerCard.tsx +182 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/ConversationSessionBar.tsx +292 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/CopyDiagnostics.tsx +24 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/Divider.tsx +47 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/FileChangesList.tsx +119 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/Markdown.tsx +198 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/ModelPopover.tsx +280 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/NoticeCard.tsx +87 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/Popover.tsx +195 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/PreviewFrame.tsx +129 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectConversation.tsx +852 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectStarters.tsx +118 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/Tooltip.tsx +191 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/TurnLog.tsx +583 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/TurnQuestionCard.tsx +133 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/dock-pane-bar.ts +14 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/project-version.ts +37 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/MentionMenuPlugin.tsx +372 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptEditor.tsx +476 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptPill.tsx +399 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptReferenceNode.tsx +164 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/index.ts +31 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/mention-menu.ts +206 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/prompt-text.ts +171 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/useStickToBottom.ts +320 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/useTicker.ts +55 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/index.ts +42 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/en-US.ts +408 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/es.ts +407 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ja.ts +407 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ko.ts +403 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/words.ts +6 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/zh-CN.ts +407 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/lookup.ts +12 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-activity-runs.ts +154 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-menu-rows.ts +41 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-refs.ts +105 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-transcript.ts +440 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/asset-lists.ts +75 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/attachment-place.ts +17 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/balance.ts +16 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-layer.ts +10 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-menu.ts +18 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-store.ts +101 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/composer-attachments.ts +189 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-attachments.ts +178 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-bridge.ts +291 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/developer.ts +39 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/esc-stop.ts +23 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/file-changes.ts +32 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/live-activity.ts +20 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/local-agent-run.ts +1174 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/local-sessions.ts +25 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/model-calls.ts +87 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/pretty-json.ts +12 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/preview-top.ts +15 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/project-resources.ts +66 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/project-sessions.ts +66 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/project-versions.ts +116 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/ref-pictures.ts +119 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/relative-time.ts +46 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/skill-pages.ts +29 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/step-file.ts +23 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/structural-share.ts +52 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-film.ts +44 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-transfer.ts +40 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/unprompted.ts +27 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/upload-limits.ts +53 -0
- package/skill/assets/upstream/apps/desktop/chat/src/mount.tsx +19 -0
- package/skill/assets/upstream/apps/desktop/chat/src/state/local-agent-store.ts +296 -0
- package/skill/assets/upstream/apps/desktop/chat/src/studio-refs.ts +307 -0
- package/skill/assets/upstream/apps/desktop/package.json +51 -0
- package/skill/assets/upstream/apps/desktop/shell/bridge.ts +415 -0
- package/skill/assets/upstream/apps/desktop/shell/index.html +27 -0
- package/skill/assets/upstream/apps/desktop/shell/main.tsx +17 -0
- package/skill/assets/upstream/apps/desktop/shell/shell.css +17 -0
- package/skill/assets/upstream/apps/desktop/shell/vite.config.ts +65 -0
- package/skill/assets/upstream/apps/desktop/src/agent-definitions.mjs +31 -0
- package/skill/assets/upstream/packages/openfilm/LICENSE +21 -0
- package/skill/assets/upstream/packages/openfilm/package.json +88 -0
- package/skill/assets/upstream/packages/openfilm/src/film-doc.d.mts +125 -0
- package/skill/assets/upstream/packages/openfilm/src/film-doc.mjs +986 -0
- package/skill/assets/upstream/packages/openfilm/src/overrides.mjs +73 -0
- package/skill/assets/upstream/packages/openfilm/src/timeline.mjs +1280 -0
- package/skill/assets/upstream/packages/openfilm/studio/server/bridge.js +209 -0
- package/skill/assets/upstream/packages/openfilm/studio/server/film-subtitle.mjs +785 -0
- package/skill/assets/upstream/packages/openfilm/studio/server/ops.mjs +403 -0
- package/skill/assets/upstream/packages/openfilm/studio/server/stage.js +1649 -0
- package/skill/assets/upstream/packages/openfilm/studio/server/track-layout.mjs +43 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/App.tsx +156 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/api.ts +328 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/AudioMeters.tsx +188 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockThumbs.tsx +84 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockWaveform.tsx +201 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLoadingScreen.tsx +56 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLogo.tsx +25 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Card.tsx +89 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ColorPicker.tsx +204 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ConfirmDialog.tsx +96 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ContextMenu.tsx +422 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/DesktopDialog.tsx +271 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ExamplesGallery.tsx +372 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmFullscreenBar.tsx +147 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmInspector.tsx +2310 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmStageSelect.tsx +3296 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FocusNav.tsx +28 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FontFamilyPicker.tsx +293 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FramePicker.tsx +69 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/InspectorPanel.tsx +25 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Launcher.tsx +38 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LayersPanel.tsx +277 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LiveTime.tsx +101 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPool.tsx +1413 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPreview.tsx +188 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MenuSelect.tsx +161 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MgOutline.tsx +24 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/OperationHistory.tsx +184 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PaneDivider.tsx +292 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PasteAttrsDialog.tsx +69 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PlaybackMenu.tsx +110 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Popover.tsx +159 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectCover.tsx +60 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectHistoryControls.tsx +748 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectLayoutMenu.tsx +82 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectMenu.tsx +111 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectSources.tsx +587 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectView.tsx +3222 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsLibrary.tsx +567 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsView.tsx +1593 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PropertiesSection.tsx +418 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ResourceInspector.tsx +100 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SelectionToolbar.tsx +157 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SkillsLibrary.tsx +504 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SourcePreview.tsx +464 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SpendAsks.tsx +88 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageMaskOverlay.tsx +299 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageTransformFrame.tsx +246 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.module.css +46 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.tsx +884 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleOverlay.tsx +148 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleRow.tsx +419 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TabStrip.tsx +188 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Timeline.tsx +4911 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TimelineMarkers.tsx +329 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Toast.tsx +55 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Tooltip.tsx +193 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/UpdateButton.tsx +104 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ViewerBar.tsx +98 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/WhatsNew.tsx +147 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/dock-pane-bar.ts +16 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportCenter.tsx +1502 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportTray.tsx +284 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/film-stage-hit.ts +18 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-fields.tsx +863 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-look.tsx +1357 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/controls-bar.tsx +397 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/transport.tsx +294 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/replace-drop.ts +20 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/AgentsSection.tsx +613 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/CustomServices.tsx +104 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/DiagnosticsGroup.tsx +202 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ModelsSection.tsx +179 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProviderMark.tsx +24 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProvidersSection.tsx +177 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsModal.tsx +511 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsSelect.tsx +84 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ShortcutsSection.tsx +291 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/StorageGroup.tsx +119 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/theme.ts +47 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ui.tsx +139 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/use-settings.ts +10 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/stage-menu.tsx +283 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/timeline-head-extras.tsx +91 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/typing-target.ts +45 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-editor-tools.tsx +408 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-page-changes.ts +48 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-project-history.ts +113 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/useBlockDrag.ts +639 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/Preview.tsx +426 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/player.ts +558 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/preview-frames.ts +36 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/steps.ts +36 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/undo.ts +156 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-film.ts +212 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-preview-renders.ts +89 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/globals.css +559 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/en.ts +1901 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/es.ts +1900 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/index.ts +130 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ja.ts +1900 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ko.ts +1896 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/words.ts +6 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/zh-CN.ts +1901 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/agent-row.ts +21 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/asset-timeline.ts +469 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/audio-levels.ts +60 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/batch-map.ts +28 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/browser.ts +10 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/canvas-fit.ts +116 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/chat-marks.ts +114 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-attrs.ts +102 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-fade.ts +70 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-look.ts +520 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-mask.ts +536 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-replace.ts +60 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/component-props.ts +128 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/context-length.ts +12 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/desktop-build.ts +44 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/diagnostics.ts +68 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/double-press.ts +22 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/dropped-overrides.ts +139 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-jobs.ts +191 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-spec.ts +261 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/field-input.ts +155 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-clip-edits.ts +82 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-frame.ts +134 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-overrides.ts +107 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-shape.ts +172 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film.ts +80 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-menu.ts +236 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-styles.ts +129 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/freeze-frame.ts +49 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/host.ts +438 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/inspect-scrub.ts +26 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap-store.ts +74 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap.ts +529 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/layers-tree.ts +133 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/look.ts +83 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/loudness.ts +58 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/markers.ts +152 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-bar.ts +58 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-folders.ts +45 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-place.ts +102 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-queue.ts +107 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-selection.ts +64 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/menu-position.ts +77 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mg-transform.ts +111 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mixed.ts +3 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/page-changes.ts +45 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/picture-files.ts +19 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-prefs.ts +46 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-renders.ts +71 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-delete.ts +20 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-folders.ts +144 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-history.ts +188 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-sources.ts +283 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/projects-page.ts +40 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/provider-marks.ts +35 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/providers.ts +163 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/resource-drag.ts +131 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/retry-image.ts +17 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved-insert.ts +40 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved.ts +125 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/selection-toolbar.ts +158 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/shortcut-hint.ts +21 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/similar-layers.ts +46 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/solo.ts +19 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-clip.ts +60 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-crop.ts +232 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-gesture.ts +277 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-layers.ts +71 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-mask.ts +176 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-measure.ts +53 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-types.ts +155 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-refs.ts +248 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-version.ts +41 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitle-cues.ts +238 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitles.ts +263 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/tab-menu.ts +6 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/template-controls.ts +131 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/theme-changes.ts +31 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cache.ts +63 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cell.ts +8 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-frame.ts +76 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-pick.ts +42 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timecode.ts +161 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-clipboard.ts +279 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-drag.ts +589 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-edit.ts +1138 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-keys.ts +63 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-layout.ts +1038 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-nav.ts +160 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-prefs.ts +73 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-split.ts +40 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-view.ts +105 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/title.ts +39 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-heights.ts +55 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-lock.ts +27 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/transitions.ts +459 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/type-metrics.ts +83 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-film-subtitles.ts +111 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-fonts.ts +181 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-loaded-thumbs.ts +91 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-edits.ts +134 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-font.ts +51 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-style.ts +68 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/waveform-columns.ts +48 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/web-update.ts +41 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/whats-new.ts +401 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/workspace-resources.ts +373 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/main.tsx +10 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/vite.config.ts +12 -0
- package/skill/assets/upstream/packages/shared/package.json +45 -0
- package/skill/assets/upstream/packages/shared/src/agent-activity.ts +263 -0
- package/skill/assets/upstream/packages/shared/src/contract.ts +285 -0
- package/skill/assets/upstream/packages/shared/src/index.ts +17 -0
- package/skill/assets/upstream/packages/shared/src/markdown-blocks.ts +252 -0
- package/skill/assets/upstream/packages/shared/src/modality.ts +4 -0
- package/skill/assets/upstream/packages/shared/src/prompt-reference.ts +113 -0
- package/skill/assets/upstream/packages/shared/src/title.ts +76 -0
- package/skill/assets/upstream/packages/shared/src/turn.ts +68 -0
- package/skill/assets/upstream/packages/shared/src/video.ts +26 -0
- package/skill/references/agent-components.md +33 -0
- package/skill/references/api-index.json +2693 -0
- package/skill/references/chat-components.md +65 -0
- package/skill/references/components.md +23 -0
- package/skill/references/core-components.md +44 -0
- package/skill/references/dependencies.md +22 -0
- package/skill/references/esm-package.md +48 -0
- package/skill/references/integration.md +43 -0
- package/skill/references/library-manifest.json +12 -0
- package/skill/references/source-manifest.json +325 -0
- package/skill/references/studio-components.md +24 -0
- package/skill/references/theme-motion.md +49 -0
- package/skill/references/troubleshooting.md +27 -0
- package/skill/references/verification.md +13 -0
- package/skill/scripts/check_dependencies.py +109 -0
- package/skill/scripts/inspect_api.py +60 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page clip's component properties: the few things of its page the person chose to offer for change, each named, as
|
|
3
|
+
* a design tool's component offers its properties. A property is made from a layer in the page (its words, its
|
|
4
|
+
* picture, whether it shows) or from one of the page's CSS variables; every clip of the page (a copy, one taken from
|
|
5
|
+
* Saved) offers the same ones, and each fills them in its own way.
|
|
6
|
+
*
|
|
7
|
+
* Where they are kept: the clip's `data-props`, a JSON list (an attribute the film keeps and gives no meaning, SPEC §1).
|
|
8
|
+
* What each clip fills in is an override, as any change made on the picture is: words as a `text` entry, a picture
|
|
9
|
+
* as the CSS that draws another file in its place, hidden as `visibility: hidden`, a variable in the `:root` entry's
|
|
10
|
+
* style. A property at its default has no override. The page itself is never written.
|
|
11
|
+
*
|
|
12
|
+
* Pure: no React, no DOM, so `node --test` runs it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { FilmOverride } from './film.ts';
|
|
16
|
+
import { nextOverrides, overrideOf } from './film-overrides.ts';
|
|
17
|
+
import { changedImage, changedText, changedVar, withImage, withText, withVar } from './template-controls.ts';
|
|
18
|
+
|
|
19
|
+
/** Where the properties are kept on the clip. */
|
|
20
|
+
export const PROPS_ATTR = 'data-props';
|
|
21
|
+
|
|
22
|
+
/** Which layer: the CSS selector in the page and which match (none: every match). */
|
|
23
|
+
type Target = { at: string; n?: number };
|
|
24
|
+
|
|
25
|
+
export type ComponentProp =
|
|
26
|
+
| ({ name: string; type: 'text' } & Target)
|
|
27
|
+
| ({ name: string; type: 'image'; kind: 'img' | 'bg' } & Target)
|
|
28
|
+
| ({ name: string; type: 'boolean' } & Target)
|
|
29
|
+
| { name: string; type: 'var'; var: string };
|
|
30
|
+
|
|
31
|
+
export type ComponentPropType = ComponentProp['type'];
|
|
32
|
+
|
|
33
|
+
/** The value a property is set to: words, a file's place from the page's folder, shown or not, a CSS value. */
|
|
34
|
+
export type PropValue = string | boolean;
|
|
35
|
+
|
|
36
|
+
const isTarget = (o: Record<string, unknown>): boolean => typeof o.at === 'string' && o.at !== '' && (o.n === undefined || (Number.isInteger(o.n) && Number(o.n) > 0));
|
|
37
|
+
|
|
38
|
+
/** The clip's properties, as its `data-props` lists them (anything not one left out). */
|
|
39
|
+
export function propsOf(clip: { attrs?: Record<string, string> } | null | undefined): ComponentProp[] {
|
|
40
|
+
const raw = clip?.attrs?.[PROPS_ATTR];
|
|
41
|
+
if (!raw) return [];
|
|
42
|
+
let list: unknown;
|
|
43
|
+
try { list = JSON.parse(raw); } catch { return []; }
|
|
44
|
+
if (!Array.isArray(list)) return [];
|
|
45
|
+
const out: ComponentProp[] = [];
|
|
46
|
+
for (const item of list) {
|
|
47
|
+
if (!item || typeof item !== 'object') continue;
|
|
48
|
+
const o = item as Record<string, unknown>;
|
|
49
|
+
if (typeof o.name !== 'string' || !o.name.trim() || out.some((p) => p.name === o.name)) continue;
|
|
50
|
+
const at = { at: o.at as string, ...(o.n !== undefined ? { n: o.n as number } : {}) };
|
|
51
|
+
if ((o.type === 'text' || o.type === 'boolean') && isTarget(o)) out.push({ name: o.name, type: o.type, ...at });
|
|
52
|
+
else if (o.type === 'image' && isTarget(o) && (o.kind === 'img' || o.kind === 'bg')) out.push({ name: o.name, type: 'image', kind: o.kind, ...at });
|
|
53
|
+
else if (o.type === 'var' && typeof o.var === 'string' && o.var.startsWith('--')) out.push({ name: o.name, type: 'var', var: o.var });
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The properties as `data-props` keeps them; null when there are none (the attribute goes). */
|
|
59
|
+
export function propsAttr(list: readonly ComponentProp[]): string | null {
|
|
60
|
+
return list.length ? JSON.stringify(list) : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A name not taken among `list`: `base`, else `base 2`, `base 3`… */
|
|
64
|
+
export function freshName(list: readonly ComponentProp[], base: string): string {
|
|
65
|
+
const name = base.trim().slice(0, 40) || 'Property';
|
|
66
|
+
for (let i = 1; ; i++) {
|
|
67
|
+
const candidate = i === 1 ? name : `${name} ${i}`;
|
|
68
|
+
if (!list.some((p) => p.name === candidate)) return candidate;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const sameTarget = (a: Target, b: Target) => a.at === b.at && (a.n ?? null) === (b.n ?? null);
|
|
73
|
+
|
|
74
|
+
/** The property made of this layer's words / picture / showing, or of this variable, when there is one. */
|
|
75
|
+
export function propOf(list: readonly ComponentProp[], type: ComponentPropType, of: Target | string): ComponentProp | null {
|
|
76
|
+
return list.find((p) => p.type === type && (p.type === 'var' ? p.var === of : typeof of !== 'string' && sameTarget(p, of))) ?? null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The list with `prop` added; one of the same type made of the same thing is replaced (its name kept unique). */
|
|
80
|
+
export function withProp(list: readonly ComponentProp[], prop: ComponentProp): ComponentProp[] {
|
|
81
|
+
const of = prop.type === 'var' ? prop.var : { at: prop.at, ...(prop.n !== undefined ? { n: prop.n } : {}) };
|
|
82
|
+
const rest = list.filter((p) => p !== propOf(list, prop.type, of));
|
|
83
|
+
return [...rest, { ...prop, name: freshName(rest, prop.name) }];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The list without the property named `name`. */
|
|
87
|
+
export function withoutProp(list: readonly ComponentProp[], name: string): ComponentProp[] {
|
|
88
|
+
return list.filter((p) => p.name !== name);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The list with a property renamed; a name taken or empty leaves it as it was. */
|
|
92
|
+
export function renamedProp(list: readonly ComponentProp[], name: string, next: string): ComponentProp[] {
|
|
93
|
+
const to = next.trim().slice(0, 40);
|
|
94
|
+
if (!to || to === name || list.some((p) => p.name === to)) return [...list];
|
|
95
|
+
return list.map((p) => (p.name === name ? { ...p, name: to } : p));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const targetOf = (p: Exclude<ComponentProp, { type: 'var' }>): Target => ({ at: p.at, ...(p.n !== undefined ? { n: p.n } : {}) });
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* What the clip set a property to (its override), or null at its default: words, a file's place from the page's folder,
|
|
102
|
+
* `false` for a layer hidden, a CSS value.
|
|
103
|
+
*/
|
|
104
|
+
export function propValue(overrides: readonly FilmOverride[] | undefined, prop: ComponentProp): PropValue | null {
|
|
105
|
+
switch (prop.type) {
|
|
106
|
+
case 'text': return changedText(overrides, prop.at, prop.n);
|
|
107
|
+
case 'image': return changedImage(overrides, { ...targetOf(prop), kind: prop.kind });
|
|
108
|
+
case 'boolean': return overrideOf(overrides, targetOf(prop))?.style?.visibility === 'hidden' ? false : null;
|
|
109
|
+
case 'var': return changedVar(overrides, prop.var);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The clip's next overrides with a property set (null: back to its default). Null when none is left. */
|
|
114
|
+
export function withPropValue(overrides: readonly FilmOverride[] | undefined, prop: ComponentProp, value: PropValue | null): FilmOverride[] | null {
|
|
115
|
+
switch (prop.type) {
|
|
116
|
+
case 'text': return withText(overrides, prop.at, prop.n, typeof value === 'string' ? value : null);
|
|
117
|
+
case 'image': return withImage(overrides, { ...targetOf(prop), kind: prop.kind }, typeof value === 'string' ? value : null);
|
|
118
|
+
case 'boolean': return nextOverrides(overrides, targetOf(prop), { style: { visibility: value === false ? 'hidden' : null } });
|
|
119
|
+
case 'var': return withVar(overrides, prop.var, typeof value === 'string' ? value : null);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The clip's next overrides with every property back to its default (its other changes, moves and fades, stay). */
|
|
124
|
+
export function withPropsReset(overrides: readonly FilmOverride[] | undefined, list: readonly ComponentProp[]): FilmOverride[] | null {
|
|
125
|
+
let next: readonly FilmOverride[] | undefined = overrides;
|
|
126
|
+
for (const p of list) if (propValue(next, p) != null) next = withPropValue(next, p, null) ?? undefined;
|
|
127
|
+
return next?.length ? [...next] : null;
|
|
128
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The context length of one of the person's own models, as typed in Settings → Agents: a whole number of tokens from
|
|
3
|
+
* 32K to 10M (apps/desktop src/pi-config.mjs contextLength), its thousands separated or not.
|
|
4
|
+
* `{}` when empty (the model gets 128K), `{ context }` when it holds, null when it does not.
|
|
5
|
+
*/
|
|
6
|
+
export function parseContextLength(text: string): { context?: number } | null {
|
|
7
|
+
const typed = text.trim();
|
|
8
|
+
if (!typed) return {};
|
|
9
|
+
if (!/^\d+$|^\d{1,3}([,. '’]\d{3})+$/.test(typed)) return null;
|
|
10
|
+
const context = Number(typed.replace(/\D/g, ''));
|
|
11
|
+
return context >= 32_000 && context <= 10_000_000 ? { context } : null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which desktop app this computer takes: its system (macOS or Windows; any other has no app) and its chip. Chromium
|
|
3
|
+
* says both outright; Safari says neither, so a Mac whose graphics are Intel's or AMD's is an Intel Mac and any other
|
|
4
|
+
* Mac is Apple silicon (the website's download button decides the same way).
|
|
5
|
+
*/
|
|
6
|
+
export type DesktopSystem = 'mac' | 'win';
|
|
7
|
+
export type DesktopArch = 'arm64' | 'x64';
|
|
8
|
+
|
|
9
|
+
type UAData = { platform?: string; getHighEntropyValues?: (hints: string[]) => Promise<{ architecture?: string; bitness?: string }> };
|
|
10
|
+
const uaData = (): UAData | undefined => (typeof navigator === 'undefined' ? undefined : (navigator as Navigator & { userAgentData?: UAData }).userAgentData);
|
|
11
|
+
|
|
12
|
+
/** This computer's system, or null when there is no desktop app for it. */
|
|
13
|
+
export function desktopSystem(): DesktopSystem | null {
|
|
14
|
+
if (typeof navigator === 'undefined') return null;
|
|
15
|
+
const platform = `${uaData()?.platform ?? ''} ${navigator.platform ?? ''} ${navigator.userAgent ?? ''}`.toLowerCase();
|
|
16
|
+
if (/iphone|ipad|android/.test(platform)) return null;
|
|
17
|
+
if (/mac/.test(platform)) return 'mac';
|
|
18
|
+
if (/win/.test(platform)) return 'win';
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A Mac's chip, from its graphics. */
|
|
23
|
+
function macChip(): DesktopArch {
|
|
24
|
+
try {
|
|
25
|
+
const gl = document.createElement('canvas').getContext('webgl');
|
|
26
|
+
const info = gl?.getExtension('WEBGL_debug_renderer_info');
|
|
27
|
+
const renderer = gl && info ? String(gl.getParameter(info.UNMASKED_RENDERER_WEBGL)) : '';
|
|
28
|
+
return /intel|amd|radeon/i.test(renderer) ? 'x64' : 'arm64';
|
|
29
|
+
} catch {
|
|
30
|
+
return 'arm64';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The system and chip whose installer this computer takes, or null when there is no desktop app for it. */
|
|
35
|
+
export async function desktopBuild(): Promise<{ os: DesktopSystem; arch: DesktopArch } | null> {
|
|
36
|
+
const os = desktopSystem();
|
|
37
|
+
if (!os) return null;
|
|
38
|
+
let arch: DesktopArch | null = null;
|
|
39
|
+
try {
|
|
40
|
+
const values = await uaData()?.getHighEntropyValues?.(['architecture', 'bitness']);
|
|
41
|
+
if (values?.architecture) arch = values.architecture === 'arm' && values.bitness !== '32' ? 'arm64' : 'x64';
|
|
42
|
+
} catch { /* not Chromium */ }
|
|
43
|
+
return { os, arch: arch ?? (os === 'mac' ? macChip() : 'x64') };
|
|
44
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The diagnostic report Settings → Developer copies: `openfilm doctor` as Studio's server runs it (src/doctor.mjs), what
|
|
3
|
+
* the browser Studio is open in can do (an agent's built-in browser can lack what Studio needs), and in the desktop
|
|
4
|
+
* app, its agents (lib/host.ts `diagnostics`).
|
|
5
|
+
*/
|
|
6
|
+
import { chromiumBased } from './browser.ts';
|
|
7
|
+
|
|
8
|
+
export interface PageFacts {
|
|
9
|
+
userAgent: string;
|
|
10
|
+
chromium: boolean;
|
|
11
|
+
/** what draws WebGL here; null without WebGL */
|
|
12
|
+
webgl: string | null;
|
|
13
|
+
/** inside another page's frame (a preview pane), where cookies and storage can be blocked */
|
|
14
|
+
framed: boolean;
|
|
15
|
+
cookies: boolean;
|
|
16
|
+
storage: boolean;
|
|
17
|
+
/** the screen and window, in CSS px, and the pixel ratio */
|
|
18
|
+
screen: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** What the page Studio is in can do, read now. */
|
|
22
|
+
export function pageFacts(): PageFacts {
|
|
23
|
+
let webgl: string | null = null;
|
|
24
|
+
try {
|
|
25
|
+
const gl = document.createElement('canvas').getContext('webgl');
|
|
26
|
+
if (gl) {
|
|
27
|
+
const info = gl.getExtension('WEBGL_debug_renderer_info');
|
|
28
|
+
webgl = String(gl.getParameter(info ? info.UNMASKED_RENDERER_WEBGL : gl.RENDERER));
|
|
29
|
+
}
|
|
30
|
+
} catch { /* none */ }
|
|
31
|
+
let storage = false;
|
|
32
|
+
try { localStorage.setItem('openfilm-doctor', '1'); localStorage.removeItem('openfilm-doctor'); storage = true; } catch { /* blocked */ }
|
|
33
|
+
let framed = false;
|
|
34
|
+
try { framed = window.top !== window; } catch { framed = true; }
|
|
35
|
+
return {
|
|
36
|
+
userAgent: navigator.userAgent,
|
|
37
|
+
chromium: chromiumBased(),
|
|
38
|
+
webgl,
|
|
39
|
+
framed,
|
|
40
|
+
cookies: navigator.cookieEnabled,
|
|
41
|
+
storage,
|
|
42
|
+
screen: `${screen.width}×${screen.height}, window ${window.innerWidth}×${window.innerHeight} @${window.devicePixelRatio}x`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The browser's lines of the report, each with ✓ or ✗ as the doctor's, and what to do about a ✗. */
|
|
47
|
+
export function browserLines(f: PageFacts): string[] {
|
|
48
|
+
const line = (ok: boolean, label: string, text: string) => `${ok ? '✓' : '✗'} ${label.padEnd(10)}${text}`;
|
|
49
|
+
const fixes: string[] = [];
|
|
50
|
+
if (!f.chromium) fixes.push('Browser: open Studio in Chrome, Edge or the desktop app; previews can fail in other browsers');
|
|
51
|
+
if (!f.webgl) fixes.push('WebGL: turn on hardware acceleration in the browser');
|
|
52
|
+
if (!f.cookies || !f.storage) fixes.push('Storage: allow cookies and site data for 127.0.0.1, or open Studio in its own tab');
|
|
53
|
+
return [
|
|
54
|
+
'Browser Studio is open in',
|
|
55
|
+
'',
|
|
56
|
+
line(f.chromium, 'Engine', f.chromium ? 'Chromium' : 'not Chromium'),
|
|
57
|
+
line(true, 'UA', f.userAgent),
|
|
58
|
+
line(Boolean(f.webgl), 'WebGL', f.webgl ?? 'unavailable'),
|
|
59
|
+
line(f.cookies && f.storage, 'Storage', `cookies ${f.cookies ? 'on' : 'off'}, site data ${f.storage ? 'on' : 'blocked'}${f.framed ? ', inside another page' : ''}`),
|
|
60
|
+
line(true, 'Screen', f.screen),
|
|
61
|
+
...(fixes.length ? ['', 'To fix:', ...fixes.map((fix) => ` ${fix}`)] : []),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The whole report: the computer's, the browser's, and the desktop app's when there is one. */
|
|
66
|
+
export function fullReport(computer: string, page: PageFacts, app?: string | null): string {
|
|
67
|
+
return [computer, browserLines(page).join('\n'), ...(app ? [app] : [])].join('\n\n────────\n\n');
|
|
68
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** A press on the picture: where, in the film's own pixels, and when (an event's timeStamp, ms). */
|
|
2
|
+
export interface Press { x: number; y: number; at: number }
|
|
3
|
+
|
|
4
|
+
/** Two presses this close in time are one double-click (the system's own interval is about this). */
|
|
5
|
+
export const DOUBLE_PRESS_MS = 500;
|
|
6
|
+
|
|
7
|
+
/** The presses kept: the last two, since a double-click is aimed by the first of its pair. */
|
|
8
|
+
export function keepPress(presses: readonly Press[], press: Press): Press[] {
|
|
9
|
+
return [...presses.slice(-1), press];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Where a double-click at `now` was aimed: its first press. The picture can move under the hand between the two (the
|
|
14
|
+
* first click opens the inspector, which shrinks it), so the second press is not where it was meant. The press just
|
|
15
|
+
* before the second one, not the first in a while: a click elsewhere a moment earlier was not aimed at it. Null when
|
|
16
|
+
* the last two presses are not the double-click's (the double-click's own point is then the best there is).
|
|
17
|
+
*/
|
|
18
|
+
export function doubleClickAim(presses: readonly Press[], now: number): Press | null {
|
|
19
|
+
const [first, second] = presses.length >= 2 ? presses.slice(-2) : [];
|
|
20
|
+
if (!first || !second) return null;
|
|
21
|
+
return second.at - first.at < DOUBLE_PRESS_MS && now - second.at < DOUBLE_PRESS_MS ? first : null;
|
|
22
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The person's changes inside pages (film.html `overrides`) that a write from outside Studio left out.
|
|
3
|
+
*
|
|
4
|
+
* Agents rewrite film.html whole. One that never read the `overrides` a person made in Studio writes the file back
|
|
5
|
+
* without them, and the edits vanish with no word (version history still has them, but nobody looks there). When
|
|
6
|
+
* film.html changes on disk from outside, Studio compares the clips that are still there: an override whose target
|
|
7
|
+
* (`at` + `n`) is gone entirely was dropped; one the writer changed was meant, and stays as written.
|
|
8
|
+
*
|
|
9
|
+
* A clip is the same clip when its name matches: its `id`, or, without one, the name the format gives it (its file's
|
|
10
|
+
* name, see clipIdFor). So an agent that leaves out an id Studio wrote still means the same clip.
|
|
11
|
+
*/
|
|
12
|
+
import { clipIdFor } from '../../../../src/film-doc.mjs';
|
|
13
|
+
import { filmDocLoc, type FilmClip, type FilmDoc, type FilmOverride } from './film.ts';
|
|
14
|
+
|
|
15
|
+
export interface DroppedEdits {
|
|
16
|
+
/** the clip as it is now: `film.html#<track>.<clip>` */
|
|
17
|
+
loc: string;
|
|
18
|
+
src: string;
|
|
19
|
+
/** what was dropped, in its old order */
|
|
20
|
+
dropped: FilmOverride[];
|
|
21
|
+
/** the clip's overrides with the dropped ones back (what a restore writes) */
|
|
22
|
+
restored: FilmOverride[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** What an override changes: the element (`at`) and which match of it (`n`). */
|
|
26
|
+
export const overrideTarget = (o: FilmOverride) => `${o.at}\u0000${o.n ?? ''}`;
|
|
27
|
+
|
|
28
|
+
/** Each clip by its name, the way the format names it: written ids first, then the rest after their files. */
|
|
29
|
+
function clipKeys(doc: FilmDoc): Map<string, { clip: FilmClip; loc: string }> {
|
|
30
|
+
const keys = new Map<string, { clip: FilmClip; loc: string }>();
|
|
31
|
+
const taken = new Set<string>();
|
|
32
|
+
doc.tracks.forEach((track) => track.clips.forEach((clip) => { if (clip.id) taken.add(clip.id); }));
|
|
33
|
+
doc.tracks.forEach((track, ti) => track.clips.forEach((clip, ci) => {
|
|
34
|
+
let name = clip.id;
|
|
35
|
+
if (!name) { name = clipIdFor(clip.src, [...taken]); taken.add(name); }
|
|
36
|
+
if (!keys.has(name)) keys.set(name, { clip, loc: filmDocLoc(ti, ci) });
|
|
37
|
+
}));
|
|
38
|
+
return keys;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function droppedOverrides(before: FilmDoc | null | undefined, after: FilmDoc | null | undefined): DroppedEdits[] {
|
|
42
|
+
if (!before?.tracks || !after?.tracks) return [];
|
|
43
|
+
const now = clipKeys(after);
|
|
44
|
+
const out: DroppedEdits[] = [];
|
|
45
|
+
for (const [key, { clip: was }] of clipKeys(before)) {
|
|
46
|
+
if (!was.overrides?.length) continue;
|
|
47
|
+
const still = now.get(key);
|
|
48
|
+
if (!still || still.clip.src !== was.src) continue; /* the clip itself went: removing it was the point */
|
|
49
|
+
const kept = new Set((still.clip.overrides ?? []).map(overrideTarget));
|
|
50
|
+
const dropped = was.overrides.filter((o) => !kept.has(overrideTarget(o)));
|
|
51
|
+
if (!dropped.length) continue;
|
|
52
|
+
out.push({ loc: still.loc, src: still.clip.src, dropped, restored: [...(still.clip.overrides ?? []), ...dropped] });
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A clip field the person set in Studio: the clip's name, the edit's prop (as the timeline and inspector send it), the value. */
|
|
58
|
+
export interface PersonEdit {
|
|
59
|
+
clip: string;
|
|
60
|
+
prop: string;
|
|
61
|
+
value: unknown;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A clip field as film.html holds it, for a prop the editor sends (`start` and `end` are the two ends of `time`). */
|
|
65
|
+
function fieldOf(clip: FilmClip, prop: string): unknown {
|
|
66
|
+
const raw = clip as unknown as Record<string, unknown>;
|
|
67
|
+
const time = Array.isArray(raw.time) ? (raw.time as number[]) : [];
|
|
68
|
+
if (prop === 'start') return time[0] ?? 0;
|
|
69
|
+
if (prop === 'end') return time[1] ?? null;
|
|
70
|
+
if (prop === 'at') return raw.at ?? 0;
|
|
71
|
+
if (prop === 'volume' || prop === 'speed') return raw[prop] ?? 1;
|
|
72
|
+
return raw[prop] ?? null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The props of a clip that are its own fields (a track's switches and moves to other tracks are not). */
|
|
76
|
+
const CLIP_FIELDS = new Set(['at', 'start', 'end', 'volume', 'speed', 'box']);
|
|
77
|
+
|
|
78
|
+
/** An edit's value as fieldOf reads the field: `null` is the default it sets back (0 for `at` and `start`, 1 for `volume` and `speed`). */
|
|
79
|
+
function editValueOf(e: PersonEdit): unknown {
|
|
80
|
+
if (e.value != null) return e.value;
|
|
81
|
+
if (e.prop === 'at' || e.prop === 'start') return 0;
|
|
82
|
+
if (e.prop === 'volume' || e.prop === 'speed') return 1;
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Two field values alike as film.html means them: key order and rounding aside (`{ x, y }` is `{ y, x }`; film.html
|
|
88
|
+
* writes times to the millisecond, and 1.2 written back as 1.2000000001 is 1.2), so a writer that only reformats
|
|
89
|
+
* changes nothing.
|
|
90
|
+
*/
|
|
91
|
+
function sameValue(a: unknown, b: unknown): boolean {
|
|
92
|
+
if (typeof a === 'number' && typeof b === 'number') return Math.abs(a - b) <= 0.0005 + 1e-9;
|
|
93
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
94
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => sameValue(v, b[i]));
|
|
95
|
+
}
|
|
96
|
+
if (a && b && typeof a === 'object' && typeof b === 'object') {
|
|
97
|
+
const x = a as Record<string, unknown>, y = b as Record<string, unknown>;
|
|
98
|
+
const keys = new Set([...Object.keys(x), ...Object.keys(y)]);
|
|
99
|
+
for (const k of keys) if (!sameValue(x[k] ?? null, y[k] ?? null)) return false;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return a === b || (a == null && b == null);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The person's own edits that a write from outside changed: an agent that wrote film.html back from what it read
|
|
107
|
+
* before sets the fields the person changed back to what they were, with no word.
|
|
108
|
+
*
|
|
109
|
+
* `edits` are every field edit the person has made (their undo history, oldest first); only the latest of each clip
|
|
110
|
+
* field counts, and only while its value is still in the film shown here (`before`): one the person undid or changed
|
|
111
|
+
* again, or one already put back or left out (`settled`), is not theirs to watch any more. They are watched across any
|
|
112
|
+
* number of outside writes, so an agent that saves once with the person's edit and later writes a stale copy without it
|
|
113
|
+
* is still caught. `undone` are the edits to make again; `gone` the ones whose clip the write removed (on purpose: not
|
|
114
|
+
* watched any more).
|
|
115
|
+
*/
|
|
116
|
+
export function undoneFields(
|
|
117
|
+
before: FilmDoc | null | undefined,
|
|
118
|
+
after: FilmDoc | null | undefined,
|
|
119
|
+
edits: readonly PersonEdit[],
|
|
120
|
+
settled: ReadonlySet<PersonEdit> = new Set(),
|
|
121
|
+
): { undone: PersonEdit[]; gone: PersonEdit[] } {
|
|
122
|
+
const undone: PersonEdit[] = [];
|
|
123
|
+
const gone: PersonEdit[] = [];
|
|
124
|
+
if (!before?.tracks || !after?.tracks) return { undone, gone };
|
|
125
|
+
const was = clipKeys(before);
|
|
126
|
+
const now = clipKeys(after);
|
|
127
|
+
const latest = new Map<string, PersonEdit>();
|
|
128
|
+
for (const e of edits) if (CLIP_FIELDS.has(e.prop)) latest.set(`${e.clip}\u0000${e.prop}`, e);
|
|
129
|
+
for (const e of latest.values()) {
|
|
130
|
+
if (settled.has(e)) continue;
|
|
131
|
+
const a = was.get(e.clip);
|
|
132
|
+
const value = editValueOf(e);
|
|
133
|
+
if (!a || !sameValue(fieldOf(a.clip, e.prop), value)) continue;
|
|
134
|
+
const b = now.get(e.clip);
|
|
135
|
+
if (!b || a.clip.src !== b.clip.src) { gone.push(e); continue; }
|
|
136
|
+
if (!sameValue(fieldOf(b.clip, e.prop), value)) undone.push(e);
|
|
137
|
+
}
|
|
138
|
+
return { undone, gone };
|
|
139
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exports run on this machine, in Studio's server (studio/server/exports.mjs): the dialog queues them, the tray
|
|
3
|
+
* follows them (the project's event stream says every change of a job), cancels them, and opens what they made.
|
|
4
|
+
*/
|
|
5
|
+
import type { FilmFrameFill, FilmFrameId } from '@/lib/film-frame';
|
|
6
|
+
import type { ExportQuality, ExportVideoCodec } from '@/lib/export-spec';
|
|
7
|
+
|
|
8
|
+
/** `burnSubtitles`: the film's subtitles drawn into the picture, as the editor shows them. */
|
|
9
|
+
type Framing = { frame?: FilmFrameId; fill?: FilmFrameFill; burnSubtitles?: boolean };
|
|
10
|
+
|
|
11
|
+
/** One export, as the server takes it. `range` is [from, to] in seconds; `name` is what its files are called (the
|
|
12
|
+
* project's name when there is none). */
|
|
13
|
+
/**
|
|
14
|
+
* How an editing-program export writes its pages. A draft: `auto` (a page with no clear pixel in any frame as H.264, the
|
|
15
|
+
* others as lossless PNG), `png`, or `prores` (422 HQ and 4444 the same way). An XML: ProRes 4444 or PNG.
|
|
16
|
+
*/
|
|
17
|
+
export type NleAlpha = 'auto' | 'png' | 'prores' | 'prores4444';
|
|
18
|
+
|
|
19
|
+
export type ExportRequest = { range?: [number, number]; label?: string; name?: string } & (
|
|
20
|
+
| (Framing & { kind: 'video'; codec: ExportVideoCodec; shortEdge?: number; fps: number; quality: ExportQuality; audio: boolean;
|
|
21
|
+
clip?: string; scale?: number; preset?: string; sidecar?: 'srt' | 'vtt' })
|
|
22
|
+
| (Framing & { kind: 'gif'; width: number; fps: number })
|
|
23
|
+
| { kind: 'audio'; format: 'wav' | 'm4a' | 'mp3'; mix: boolean; stems: boolean }
|
|
24
|
+
| (Framing & { kind: 'stills'; format: 'png' | 'jpg'; shortEdge: number; at?: number[] })
|
|
25
|
+
| (Framing & { kind: 'poster'; format: 'png' | 'jpg'; shortEdge: number; at?: number })
|
|
26
|
+
| { kind: 'subtitles'; format: 'srt' | 'vtt' | 'txt' }
|
|
27
|
+
| (Framing & { kind: 'slides'; format: 'pptx' | 'pdf'; shortEdge: number; at?: number[] })
|
|
28
|
+
/* for an editing program: an XML and media folder; `media: 'link'` points at the sources where they are instead of cloning them */
|
|
29
|
+
| { kind: 'nle'; format?: 'xml' | 'fcpxml' | 'jianying' | 'capcut'; fps: number; audio: 'clips' | 'stems'; media?: 'copy' | 'link'; subtitles: boolean;
|
|
30
|
+
/* how the pages, rendered with their alpha, are written */
|
|
31
|
+
alpha?: NleAlpha }
|
|
32
|
+
/* the project itself: its work, and the parts of its .film asked; a zip of its folder, or the folder */
|
|
33
|
+
| { kind: 'project'; parts: ProjectPart[]; as: 'zip' | 'folder' }
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/** What a project's export may take besides its work (studio/server/projects.mjs PROJECT_PARTS). */
|
|
37
|
+
export type ProjectPart = 'history' | 'chat' | 'logs';
|
|
38
|
+
export const PROJECT_PARTS: readonly ProjectPart[] = ['history', 'chat', 'logs'];
|
|
39
|
+
|
|
40
|
+
/** How big a project's export is: its work, and each part it has (null: none), in bytes. */
|
|
41
|
+
export type ProjectSizes = { work: number } & Record<ProjectPart, number | null>;
|
|
42
|
+
|
|
43
|
+
export type ExportJobStatus = 'waiting' | 'running' | 'done' | 'failed' | 'cancelled';
|
|
44
|
+
|
|
45
|
+
export interface ExportJob {
|
|
46
|
+
id: string;
|
|
47
|
+
project: string;
|
|
48
|
+
/** The project's name. */
|
|
49
|
+
name: string;
|
|
50
|
+
/** What the dialog called it ("YouTube", "ProRes 4444", "Full mix"…); empty for an export asked elsewhere. */
|
|
51
|
+
label: string;
|
|
52
|
+
kind: ExportRequest['kind'];
|
|
53
|
+
status: ExportJobStatus;
|
|
54
|
+
phase: 'preparing' | 'audio' | 'rendering' | 'encoding' | 'copying' | 'finishing' | null;
|
|
55
|
+
/** 0 to 1. */
|
|
56
|
+
progress: number;
|
|
57
|
+
framesDone: number;
|
|
58
|
+
framesTotal: number;
|
|
59
|
+
/** Frames per second drawn. */
|
|
60
|
+
rate: number;
|
|
61
|
+
etaMs: number | null;
|
|
62
|
+
encoder: string | null;
|
|
63
|
+
/** Absolute paths of the files made (one, or the mix and its stems). */
|
|
64
|
+
outputs: string[];
|
|
65
|
+
bytes: number | null;
|
|
66
|
+
/** Whether a transparent export lets anything through (sampled frames); null when not measured. */
|
|
67
|
+
alpha: { any: boolean } | null;
|
|
68
|
+
/** A draft's app's drafts folder that refused the write: the draft is in the exports folder instead. */
|
|
69
|
+
draftsRefused?: string;
|
|
70
|
+
/** The export stopped because the disk is full. */
|
|
71
|
+
diskFull?: boolean;
|
|
72
|
+
error?: string;
|
|
73
|
+
/** Cancel was asked and the export is stopping (still `running` until it has). */
|
|
74
|
+
cancelling?: boolean;
|
|
75
|
+
at: number;
|
|
76
|
+
startedAt: number | null;
|
|
77
|
+
finishedAt: number | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** What this machine's ffmpeg encodes, for the choices that depend on it. */
|
|
81
|
+
export interface ExportCapabilities {
|
|
82
|
+
h264: boolean;
|
|
83
|
+
hevc: boolean;
|
|
84
|
+
hevcAlpha: boolean;
|
|
85
|
+
vp9: boolean;
|
|
86
|
+
prores: boolean;
|
|
87
|
+
mp3: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const endpoint = (projectId: string) => `/api/projects/${encodeURIComponent(projectId)}/exports`;
|
|
91
|
+
|
|
92
|
+
async function call<T>(url: string, init?: RequestInit & { json?: unknown }): Promise<T> {
|
|
93
|
+
const res = await fetch(url, {
|
|
94
|
+
...init,
|
|
95
|
+
...(init?.json !== undefined ? { headers: { 'content-type': 'application/json' }, body: JSON.stringify(init.json) } : {}),
|
|
96
|
+
});
|
|
97
|
+
const body = await res.json().catch(() => ({})) as { error?: string };
|
|
98
|
+
if (!res.ok) throw new Error(body.error || `Studio answered ${res.status}`);
|
|
99
|
+
return body as T;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The project's exports, where they go by default, what this machine encodes, how many subtitle cues the film has, and
|
|
104
|
+
* how big the project and its parts are.
|
|
105
|
+
*/
|
|
106
|
+
export function listExports(projectId: string): Promise<{ exports: ExportJob[]; folder: string; can: ExportCapabilities; drafts?: { jianying: boolean; capcut: boolean }; subtitles?: number; project?: ProjectSizes }> {
|
|
107
|
+
return call(endpoint(projectId), { cache: 'no-store' });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** A video file's frame rate (`media?what=probe`); null when it has none or can't be read. */
|
|
111
|
+
export async function probeFps(projectId: string, path: string): Promise<number | null> {
|
|
112
|
+
const res = await fetch(`/api/projects/${encodeURIComponent(projectId)}/media?what=probe&path=${encodeURIComponent(path)}`, { cache: 'no-store' });
|
|
113
|
+
if (!res.ok) return null;
|
|
114
|
+
const body = await res.json().catch(() => null) as { fps?: number } | null;
|
|
115
|
+
return typeof body?.fps === 'number' && body.fps > 0 ? body.fps : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Queue exports, all or none. `folder`: where to write them (absolute, or ~/…); missing = the default folder. */
|
|
119
|
+
export async function startExports(projectId: string, jobs: readonly ExportRequest[], folder?: string): Promise<ExportJob[]> {
|
|
120
|
+
const body = await call<{ exports: ExportJob[] }>(endpoint(projectId), { method: 'POST', json: { jobs, ...(folder ? { folder } : {}) } });
|
|
121
|
+
return body.exports;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function cancelExport(projectId: string, jobId: string): Promise<void> {
|
|
125
|
+
await call(`${endpoint(projectId)}/${encodeURIComponent(jobId)}`, { method: 'DELETE' });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Show the export's file in the system's file manager. */
|
|
129
|
+
export async function revealExport(projectId: string, jobId: string): Promise<void> {
|
|
130
|
+
await call(`${endpoint(projectId)}/${encodeURIComponent(jobId)}/reveal`, { method: 'POST', json: {} });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Open the export's file in the app the system opens it with. */
|
|
134
|
+
export async function openExport(projectId: string, jobId: string): Promise<void> {
|
|
135
|
+
await call(`${endpoint(projectId)}/${encodeURIComponent(jobId)}/open`, { method: 'POST', json: {} });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function jobIsActive(job: ExportJob): boolean {
|
|
139
|
+
return job.status === 'waiting' || job.status === 'running';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function basenameOf(path: string): string {
|
|
143
|
+
return path.split(/[\\/]/).filter(Boolean).pop() ?? path;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function formatBytes(n: number): string {
|
|
147
|
+
if (!Number.isFinite(n) || n <= 0) return '—';
|
|
148
|
+
if (n < 1024 * 1024) return `${Math.max(1, Math.round(n / 1024))} KB`;
|
|
149
|
+
if (n < 1024 * 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(n < 10 * 1024 * 1024 ? 1 : 0)} MB`;
|
|
150
|
+
return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function formatDuration(ms: number): string {
|
|
154
|
+
const s = Math.max(0, Math.round(ms / 1000));
|
|
155
|
+
const h = Math.floor(s / 3600);
|
|
156
|
+
const m = Math.floor((s % 3600) / 60);
|
|
157
|
+
const sec = s % 60;
|
|
158
|
+
return h > 0
|
|
159
|
+
? `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`
|
|
160
|
+
: `${m}:${String(sec).padStart(2, '0')}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A moment or a length of the film in the export dialog: m:ss, with tenths where it is not a whole second (0:07.5),
|
|
165
|
+
* so a film of 7.5 s doesn't read as 0:08.
|
|
166
|
+
*/
|
|
167
|
+
export function formatFilmTime(ms: number): string {
|
|
168
|
+
const tenths = Math.max(0, Math.round(ms / 100));
|
|
169
|
+
const whole = formatDuration(Math.floor(tenths / 10) * 1000);
|
|
170
|
+
return tenths % 10 ? `${whole}.${tenths % 10}` : whole;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* What the tray's head says once nothing runs: how the exports ended, in the person's words. `tone` picks its mark:
|
|
175
|
+
* a failure needs reading; all cancelled is neither good nor bad; anything made is done.
|
|
176
|
+
*/
|
|
177
|
+
export function endedHeadline(jobs: readonly ExportJob[], t: (key: string) => string): { tone: 'ok' | 'err' | 'muted'; text: string } {
|
|
178
|
+
const n = (status: ExportJobStatus) => jobs.filter((j) => j.status === status).length;
|
|
179
|
+
const done = n('done');
|
|
180
|
+
const cancelled = n('cancelled');
|
|
181
|
+
const failed = n('failed');
|
|
182
|
+
if (failed === jobs.length) return { tone: 'err', text: t(failed === 1 ? 'exportTray.failedOne' : 'exportTray.failedAll') };
|
|
183
|
+
if (cancelled === jobs.length) return { tone: 'muted', text: t(cancelled === 1 ? 'exportTray.cancelledOne' : 'exportTray.cancelledAll') };
|
|
184
|
+
if (done === jobs.length) return { tone: 'ok', text: t('exportTray.finished') };
|
|
185
|
+
const parts = [
|
|
186
|
+
done ? t('exportTray.countDone').replace('{n}', String(done)) : '',
|
|
187
|
+
cancelled ? t('exportTray.countCancelled').replace('{n}', String(cancelled)) : '',
|
|
188
|
+
failed ? t('exportTray.countFailed').replace('{n}', String(failed)) : '',
|
|
189
|
+
].filter(Boolean);
|
|
190
|
+
return { tone: failed ? 'err' : done ? 'ok' : 'muted', text: parts.join(', ') };
|
|
191
|
+
}
|