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,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Layers panel's tree (components/LayersPanel): the picture clips shown at the playhead, the top one first, and a
|
|
3
|
+
* page's layers inside it, each level in the order it is drawn (the one on top first), as the stage reads them
|
|
4
|
+
* (studio/server/stage.js `tree`). Reordering a level writes z-index overrides (stage.js `restack`).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type LayerRowKind = 'text' | 'image' | 'shape' | 'box' | 'group';
|
|
8
|
+
|
|
9
|
+
export interface LayerRow {
|
|
10
|
+
/** The element's handle in the page (good while the page is loaded). */
|
|
11
|
+
handle: number;
|
|
12
|
+
/** The handle of the element the row is restacked by: the child of its parent row's element that holds it. */
|
|
13
|
+
branch: number;
|
|
14
|
+
/** Its selector, with `#n` when the selector finds several (the override target). */
|
|
15
|
+
loc: string;
|
|
16
|
+
instance?: number;
|
|
17
|
+
tag: string;
|
|
18
|
+
kind: LayerRowKind;
|
|
19
|
+
label: string;
|
|
20
|
+
/** Hidden by the person (an override's visibility: hidden). */
|
|
21
|
+
hidden: boolean;
|
|
22
|
+
locked: boolean;
|
|
23
|
+
/** Not drawn at this moment (faded out, hidden by the page, no size). */
|
|
24
|
+
off: boolean;
|
|
25
|
+
children: LayerRow[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LayerClip {
|
|
29
|
+
clip: string;
|
|
30
|
+
kind: 'page' | 'video' | 'image' | string;
|
|
31
|
+
layers: LayerRow[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** One line of the panel as drawn: a clip, or a layer `depth` levels in, with the rows it sits among. */
|
|
35
|
+
export interface LayerLine {
|
|
36
|
+
key: string;
|
|
37
|
+
clipId: string;
|
|
38
|
+
depth: number;
|
|
39
|
+
row: LayerRow | null;
|
|
40
|
+
/** The level it is in (its parent's children), and its place there; none for a clip. */
|
|
41
|
+
siblings: readonly LayerRow[];
|
|
42
|
+
index: number;
|
|
43
|
+
/** The key of the line its level is under. */
|
|
44
|
+
parentKey: string;
|
|
45
|
+
open: boolean;
|
|
46
|
+
canOpen: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const clipKey = (clipId: string) => `clip:${clipId}`;
|
|
50
|
+
export const layerKey = (clipId: string, loc: string) => `layer:${clipId}:${loc}`;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether a line starts open: a clip does, and a row alone in its level (a page's one wrapper is no stop on the way
|
|
54
|
+
* in); the person's `toggled` lines are the other way round.
|
|
55
|
+
*/
|
|
56
|
+
const startsOpen = (siblings: number | null) => siblings == null || siblings === 1;
|
|
57
|
+
|
|
58
|
+
/** The lines shown: each clip, then the layers of the open ones and of their open rows. */
|
|
59
|
+
export function layerLines(clips: readonly LayerClip[], toggled: ReadonlySet<string>): LayerLine[] {
|
|
60
|
+
const out: LayerLine[] = [];
|
|
61
|
+
const walk = (clipId: string, rows: readonly LayerRow[], depth: number, parentKey: string) => {
|
|
62
|
+
rows.forEach((row, index) => {
|
|
63
|
+
const key = layerKey(clipId, row.loc);
|
|
64
|
+
const canOpen = row.children.length > 0;
|
|
65
|
+
const isOpen = canOpen && startsOpen(rows.length) !== toggled.has(key);
|
|
66
|
+
out.push({ key, clipId, depth, row, siblings: rows, index, parentKey, open: isOpen, canOpen });
|
|
67
|
+
if (isOpen) walk(clipId, row.children, depth + 1, key);
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
for (const c of clips) {
|
|
71
|
+
const key = clipKey(c.clip);
|
|
72
|
+
const canOpen = c.layers.length > 0;
|
|
73
|
+
const isOpen = canOpen && startsOpen(null) !== toggled.has(key);
|
|
74
|
+
out.push({ key, clipId: c.clip, depth: 0, row: null, siblings: [], index: 0, parentKey: '', open: isOpen, canOpen });
|
|
75
|
+
if (isOpen) walk(c.clip, c.layers, 1, key);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The person's toggled lines with the clip and the rows around the layer at `loc` open (the same set when they are). */
|
|
81
|
+
export function openTo(clips: readonly LayerClip[], toggled: ReadonlySet<string>, clipId: string, loc: string): ReadonlySet<string> {
|
|
82
|
+
const c = clips.find((x) => x.clip === clipId);
|
|
83
|
+
if (!c) return toggled;
|
|
84
|
+
const around: Array<{ key: string; siblings: number | null }> = [];
|
|
85
|
+
const find = (rows: readonly LayerRow[]): boolean => rows.some((row) => {
|
|
86
|
+
if (row.loc === loc) return true;
|
|
87
|
+
around.push({ key: layerKey(clipId, row.loc), siblings: rows.length });
|
|
88
|
+
if (find(row.children)) return true;
|
|
89
|
+
around.pop();
|
|
90
|
+
return false;
|
|
91
|
+
});
|
|
92
|
+
if (!find(c.layers)) return toggled;
|
|
93
|
+
const shut = [{ key: clipKey(clipId), siblings: null }, ...around].filter((l) => startsOpen(l.siblings) === toggled.has(l.key));
|
|
94
|
+
if (!shut.length) return toggled;
|
|
95
|
+
const next = new Set(toggled);
|
|
96
|
+
for (const l of shut) { if (next.has(l.key)) next.delete(l.key); else next.add(l.key); }
|
|
97
|
+
return next;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The line that moves when a dragged row is let go over line `over`: CSS restacks only within a level, so a row can be
|
|
102
|
+
* dropped in its own level or in the level of any row around it, and there the row around it moves, with all it holds.
|
|
103
|
+
* Null when `over` is in none of those levels (another clip, a group it is not in).
|
|
104
|
+
*/
|
|
105
|
+
export function moverFor(lines: readonly LayerLine[], dragged: LayerLine, over: LayerLine): LayerLine | null {
|
|
106
|
+
const byKey = new Map(lines.map((l) => [l.key, l]));
|
|
107
|
+
/* per level (the key of the line it is under), the row of the dragged one's chain in it */
|
|
108
|
+
const chain = new Map<string, LayerLine>();
|
|
109
|
+
for (let c: LayerLine | undefined = dragged; c?.row; c = byKey.get(c.parentKey)) chain.set(c.parentKey, c);
|
|
110
|
+
for (let c: LayerLine | undefined = over; c?.row; c = byKey.get(c.parentKey)) {
|
|
111
|
+
const mover = chain.get(c.parentKey);
|
|
112
|
+
if (mover) return mover;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A level reordered: the row at `from` dropped at `to` (a gap, 0 above the first row … length below the last), as the
|
|
119
|
+
* handles to restack by, the top one first. Null when it lands where it was.
|
|
120
|
+
*/
|
|
121
|
+
export function movedOrder(siblings: readonly LayerRow[], from: number, to: number): number[] | null {
|
|
122
|
+
if (from < 0 || from >= siblings.length) return null;
|
|
123
|
+
const at = to > from ? to - 1 : to;
|
|
124
|
+
if (at === from) return null;
|
|
125
|
+
const rest = siblings.filter((_, i) => i !== from);
|
|
126
|
+
rest.splice(Math.max(0, Math.min(rest.length, at)), 0, siblings[from]!);
|
|
127
|
+
return rest.map((r) => r.branch);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Two reads of the tree are the same picture of it (the panel redraws only when they differ). */
|
|
131
|
+
export function sameTree(a: readonly LayerClip[] | null, b: readonly LayerClip[] | null): boolean {
|
|
132
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
133
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inspector's look controls over one or several things (layers inside a page, or clips): what each reads of their
|
|
3
|
+
* CSS, and how an edit is shown (previewed) and written (committed) for each of them.
|
|
4
|
+
*/
|
|
5
|
+
import { MIXED, type Mixed } from './mixed.ts';
|
|
6
|
+
|
|
7
|
+
/** One thing's CSS property (kebab-case), as it is now. */
|
|
8
|
+
export type LookRead = (prop: string) => string | undefined;
|
|
9
|
+
/** Properties to write: a value, or null — "none of mine" (the panel decides what that leaves). */
|
|
10
|
+
export type LookSet = Record<string, string | null>;
|
|
11
|
+
/** What a control does to one thing, from that thing's own values. */
|
|
12
|
+
export type LookEdit = (read: LookRead) => LookSet;
|
|
13
|
+
|
|
14
|
+
export interface Look {
|
|
15
|
+
/** How many things it is. */
|
|
16
|
+
n: number;
|
|
17
|
+
/** What `fn` makes of every thing's values, when they agree; MIXED when they do not. */
|
|
18
|
+
common<V>(fn: (read: LookRead) => V): V | Mixed;
|
|
19
|
+
/** Shows an edit on the picture, nothing written. */
|
|
20
|
+
preview(edit: LookEdit): void;
|
|
21
|
+
/** Writes an edit, as one change. */
|
|
22
|
+
commit(edit: LookEdit): void;
|
|
23
|
+
/** Takes a preview back off the picture. */
|
|
24
|
+
revert(): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A Look over `targets`: read each through `readOf`, and hand each one's properties to `write` (the panel stores
|
|
29
|
+
* them). `write` gets only the things an edit changes.
|
|
30
|
+
*/
|
|
31
|
+
export function makeLook<Tg>(
|
|
32
|
+
targets: readonly Tg[],
|
|
33
|
+
readOf: (t: Tg) => LookRead,
|
|
34
|
+
write: {
|
|
35
|
+
commit: (sets: [Tg, LookSet][]) => void;
|
|
36
|
+
preview?: (sets: [Tg, LookSet][]) => void;
|
|
37
|
+
revert?: () => void;
|
|
38
|
+
},
|
|
39
|
+
): Look {
|
|
40
|
+
const reads = targets.map(readOf);
|
|
41
|
+
const sets = (edit: LookEdit, kept?: ReadonlyMap<string, string | undefined>[] | null): [Tg, LookSet][] => targets.flatMap((tg, i) => {
|
|
42
|
+
const read = reads[i]!;
|
|
43
|
+
/* what was there before a preview: the layer is measured again while previewed, so by now it reads as the
|
|
44
|
+
preview. The edit is worked out from it (a preview may have changed what the edit decides by, as text color
|
|
45
|
+
taking over the words' own fill), and unchanged properties are not written: the agent reads the diff, a no-op
|
|
46
|
+
would be an undo step, and a kept preview would read as no change */
|
|
47
|
+
const was = (k: string) => (kept?.[i]?.has(k) ? kept[i]!.get(k) : read(k));
|
|
48
|
+
const set = edit(was);
|
|
49
|
+
const changed = Object.entries(set).filter(([k, v]) => (v ?? undefined) !== was(k));
|
|
50
|
+
return changed.length ? [[tg, Object.fromEntries(changed)] as [Tg, LookSet]] : [];
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
n: targets.length,
|
|
54
|
+
common(fn) {
|
|
55
|
+
if (!reads.length) return MIXED;
|
|
56
|
+
const first = fn(reads[0]!);
|
|
57
|
+
const key = JSON.stringify(first);
|
|
58
|
+
return reads.every((r) => JSON.stringify(fn(r)) === key) ? first : MIXED;
|
|
59
|
+
},
|
|
60
|
+
/* shown as said, unchanged or not: a layer reads as its kept change while a preview paints over it, so painting
|
|
61
|
+
back what was there would read as no change */
|
|
62
|
+
preview(edit) {
|
|
63
|
+
if (!previewedBefore || previewedBefore.length !== targets.length) previewedBefore = targets.map(() => new Map());
|
|
64
|
+
const list = targets.flatMap((tg, i) => {
|
|
65
|
+
const set = edit(reads[i]!);
|
|
66
|
+
for (const k of Object.keys(set)) if (!previewedBefore![i]!.has(k)) previewedBefore![i]!.set(k, reads[i]!(k));
|
|
67
|
+
return Object.keys(set).length ? [[tg, set] as [Tg, LookSet]] : [];
|
|
68
|
+
});
|
|
69
|
+
write.preview?.(list);
|
|
70
|
+
},
|
|
71
|
+
commit(edit) {
|
|
72
|
+
const list = sets(edit, previewedBefore);
|
|
73
|
+
previewedBefore = null;
|
|
74
|
+
if (list.length) write.commit(list);
|
|
75
|
+
},
|
|
76
|
+
revert() { previewedBefore = null; write.revert?.(); },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** What each previewed property of the things in hand was before its first preview (see makeLook's `sets`). */
|
|
81
|
+
let previewedBefore: Map<string, string | undefined>[] | null = null;
|
|
82
|
+
/** A preview was painted back by hand (as a preview of what was there): nothing previewed stands any more. */
|
|
83
|
+
export const previewSettled = () => { previewedBefore = null; };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loudness normalization: a clip's `volume` set so the part of its file it plays measures a target loudness. Studio
|
|
3
|
+
* measures the integrated loudness of that part (EBU R128, LUFS; studio/server/derived.mjs `loudness`); the gain is
|
|
4
|
+
* the difference, within the volume a clip can have (0–500%, the inspector's range).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** The targets offered: web video, podcasts, broadcast (EBU R128). */
|
|
8
|
+
export const LOUDNESS_TARGETS = [
|
|
9
|
+
{ id: 'web', lufs: -14 },
|
|
10
|
+
{ id: 'podcast', lufs: -16 },
|
|
11
|
+
{ id: 'broadcast', lufs: -23 },
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
/** The loudest a clip's volume goes (500%), as the inspector's slider. */
|
|
15
|
+
export const VOLUME_MAX = 5;
|
|
16
|
+
|
|
17
|
+
/** The volume slider's range in dB: its bottom is silence, its top the loudest a clip goes (500%, +14 dB). */
|
|
18
|
+
export const VOLUME_DB_MIN = -60;
|
|
19
|
+
export const VOLUME_DB_MAX = Math.round(20 * Math.log10(VOLUME_MAX) * 10) / 10;
|
|
20
|
+
|
|
21
|
+
/** A clip's volume as the slider shows it: dB to the tenth, silence at the bottom. */
|
|
22
|
+
export function volumeToDb(volume: number): number {
|
|
23
|
+
if (!(volume > 0)) return VOLUME_DB_MIN;
|
|
24
|
+
const db = Math.round(20 * Math.log10(volume) * 10) / 10;
|
|
25
|
+
return Math.max(VOLUME_DB_MIN, Math.min(VOLUME_DB_MAX, db));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The volume a dB on the slider writes, to the thousandth: silent at its bottom, at most 500%. */
|
|
29
|
+
export function dbToVolume(db: number): number {
|
|
30
|
+
if (!(db > VOLUME_DB_MIN)) return 0;
|
|
31
|
+
return Math.round(Math.min(VOLUME_MAX, 10 ** (db / 20)) * 1000) / 1000;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Quieter than this is silence: EBU R128 gates out anything under −70 LUFS. */
|
|
35
|
+
const SILENT_LUFS = -70;
|
|
36
|
+
|
|
37
|
+
export type NormalizePlan =
|
|
38
|
+
| { volume: number; reached: true }
|
|
39
|
+
/** the volume it would need, past what a clip can have: it is set to the most it can, and that is said */
|
|
40
|
+
| { volume: number; reached: false; wantedVolume: number; lufs: number }
|
|
41
|
+
| { silent: true };
|
|
42
|
+
|
|
43
|
+
/** The volume that brings `measured` LUFS to `target` LUFS, kept to 0–500%, to the thousandth. */
|
|
44
|
+
export function normalizeVolume(measured: number | null, target: number): NormalizePlan {
|
|
45
|
+
if (measured == null || !Number.isFinite(measured) || measured <= SILENT_LUFS) return { silent: true };
|
|
46
|
+
const wanted = 10 ** ((target - measured) / 20);
|
|
47
|
+
const volume = Math.round(Math.min(VOLUME_MAX, wanted) * 1000) / 1000;
|
|
48
|
+
if (wanted <= VOLUME_MAX + 0.0005) return { volume, reached: true };
|
|
49
|
+
/* the loudness it gets at the most it can have */
|
|
50
|
+
const lufs = Math.round((measured + 20 * Math.log10(VOLUME_MAX)) * 10) / 10;
|
|
51
|
+
return { volume, reached: false, wantedVolume: Math.round(wanted * 1000) / 1000, lufs };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The part of a clip's file it plays, in the file's seconds: what is measured. */
|
|
55
|
+
export function measuredSpan(clip: { inMs?: number; speed?: number; startMs: number; endMs: number }): { from: number; to: number } {
|
|
56
|
+
const from = (clip.inMs ?? 0) / 1000;
|
|
57
|
+
return { from, to: from + ((clip.endMs - clip.startMs) * (clip.speed ?? 1)) / 1000 };
|
|
58
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markers: named, colored places on the film (M on the ruler), or on a clip (M with a clip selected under the
|
|
3
|
+
* playhead), which then moves with the clip. Studio's own, kept in `.film/markers.json` (studio/server/markers.mjs),
|
|
4
|
+
* never in film.html: a marker changes nothing in the film.
|
|
5
|
+
*
|
|
6
|
+
* A film marker is a film second. A clip marker is a second of its clip's file, so it stays on the same moment of the
|
|
7
|
+
* footage when the clip is moved or trimmed; it is shown only while that moment is in the clip.
|
|
8
|
+
*/
|
|
9
|
+
import { snapToFrame } from './timecode.ts';
|
|
10
|
+
import type { TimelineBlock, TimelineTrack } from './timeline-layout';
|
|
11
|
+
|
|
12
|
+
export const MARKER_COLORS = ['blue', 'green', 'yellow', 'orange', 'red', 'purple'] as const;
|
|
13
|
+
export type MarkerColor = (typeof MARKER_COLORS)[number];
|
|
14
|
+
|
|
15
|
+
/** The colors as the timeline paints them. */
|
|
16
|
+
export const MARKER_PAINT: Record<MarkerColor, string> = {
|
|
17
|
+
blue: '#4c8dff',
|
|
18
|
+
green: '#3fbf6f',
|
|
19
|
+
yellow: '#e8c547',
|
|
20
|
+
orange: '#f08a3c',
|
|
21
|
+
red: '#e5484d',
|
|
22
|
+
purple: '#a57cf0',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export interface Marker {
|
|
26
|
+
id: string;
|
|
27
|
+
/** Film seconds; on a clip, seconds of the clip's file. */
|
|
28
|
+
t: number;
|
|
29
|
+
/** The clip it is on (film.html id); absent for a marker on the film. */
|
|
30
|
+
clip?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
color: MarkerColor;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The most a project keeps, and the longest name. */
|
|
36
|
+
export const MARKERS_MAX = 1000;
|
|
37
|
+
export const MARKER_NAME_MAX = 80;
|
|
38
|
+
|
|
39
|
+
const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
40
|
+
|
|
41
|
+
/** Markers as kept: what is not one is left out (the server keeps the same rule, see markers.mjs). */
|
|
42
|
+
export function parseMarkers(raw: unknown): Marker[] {
|
|
43
|
+
if (!Array.isArray(raw)) return [];
|
|
44
|
+
const out: Marker[] = [];
|
|
45
|
+
const ids = new Set<string>();
|
|
46
|
+
for (const m of raw) {
|
|
47
|
+
if (!isRecord(m) || typeof m.id !== 'string' || !m.id || ids.has(m.id)) continue;
|
|
48
|
+
if (typeof m.t !== 'number' || !Number.isFinite(m.t) || m.t < 0) continue;
|
|
49
|
+
ids.add(m.id);
|
|
50
|
+
const name = typeof m.name === 'string' ? m.name.trim().slice(0, MARKER_NAME_MAX) : '';
|
|
51
|
+
out.push({
|
|
52
|
+
id: m.id,
|
|
53
|
+
t: Math.round(m.t * 1000) / 1000,
|
|
54
|
+
...(typeof m.clip === 'string' && m.clip ? { clip: m.clip } : {}),
|
|
55
|
+
...(name ? { name } : {}),
|
|
56
|
+
color: (MARKER_COLORS as readonly string[]).includes(String(m.color)) ? (m.color as MarkerColor) : 'blue',
|
|
57
|
+
});
|
|
58
|
+
if (out.length >= MARKERS_MAX) break;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** An id no marker has. */
|
|
64
|
+
export function markerId(list: readonly Marker[]): string {
|
|
65
|
+
const taken = new Set(list.map((m) => m.id));
|
|
66
|
+
let n = list.length + 1;
|
|
67
|
+
while (taken.has(`m${n}`)) n++;
|
|
68
|
+
return `m${n}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A clip as a marker on it needs it: where it is on the film and which part of its file it plays. */
|
|
72
|
+
type ClipPlace = Pick<TimelineBlock, 'clipId' | 'startMs' | 'endMs' | 'inMs' | 'speed'>;
|
|
73
|
+
|
|
74
|
+
/** Where a clip marker is on the film (ms), or null while that moment of the file is not in the clip. */
|
|
75
|
+
export function clipMarkerMs(m: Marker, clip: ClipPlace): number | null {
|
|
76
|
+
const ms = clip.startMs + (m.t * 1000 - (clip.inMs ?? 0)) / (clip.speed ?? 1);
|
|
77
|
+
return ms >= clip.startMs - 0.5 && ms <= clip.endMs + 0.5 ? ms : null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A marker on the film as it is drawn: where (ms), and the clip it is on. */
|
|
81
|
+
export type PlacedMarker = { marker: Marker; ms: number; clip?: TimelineBlock };
|
|
82
|
+
|
|
83
|
+
/** Every marker that shows, with its film time, in time order. A clip marker shows while its clip and moment do. */
|
|
84
|
+
export function placeMarkers(list: readonly Marker[], tracks: readonly TimelineTrack[]): PlacedMarker[] {
|
|
85
|
+
const clips = new Map<string, TimelineBlock>();
|
|
86
|
+
for (const tr of tracks) for (const b of tr.blocks) if (b.clipId && b.loc) clips.set(b.clipId, b);
|
|
87
|
+
const out: PlacedMarker[] = [];
|
|
88
|
+
for (const marker of list) {
|
|
89
|
+
if (!marker.clip) { out.push({ marker, ms: marker.t * 1000 }); continue; }
|
|
90
|
+
const clip = clips.get(marker.clip);
|
|
91
|
+
const ms = clip ? clipMarkerMs(marker, clip) : null;
|
|
92
|
+
if (clip && ms != null) out.push({ marker, ms, clip });
|
|
93
|
+
}
|
|
94
|
+
return out.sort((a, b) => a.ms - b.ms);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The markers once one is added at `atMs` (on a frame): on `clip` when given, else on the film. A marker already on
|
|
99
|
+
* that frame is not added twice; `added` is the one there.
|
|
100
|
+
*/
|
|
101
|
+
export function addMarker(list: readonly Marker[], atMs: number, clip?: ClipPlace | null, color: MarkerColor = 'blue'): { list: Marker[]; added: Marker } {
|
|
102
|
+
const ms = snapToFrame(Math.max(0, atMs));
|
|
103
|
+
const t = clip?.clipId
|
|
104
|
+
? Math.round(((clip.inMs ?? 0) + (ms - clip.startMs) * (clip.speed ?? 1))) / 1000
|
|
105
|
+
: Math.round(ms) / 1000;
|
|
106
|
+
const same = list.find((m) => (m.clip ?? null) === (clip?.clipId ?? null) && Math.abs(m.t - t) < 0.0005);
|
|
107
|
+
if (same) return { list: [...list], added: same };
|
|
108
|
+
const added: Marker = { id: markerId(list), t: Math.max(0, t), ...(clip?.clipId ? { clip: clip.clipId } : {}), color };
|
|
109
|
+
return { list: [...list, added], added };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** One marker changed: its name (empty takes it away), color, or film time. */
|
|
113
|
+
export function changeMarker(list: readonly Marker[], id: string, change: { name?: string; color?: MarkerColor; t?: number }): Marker[] {
|
|
114
|
+
return list.map((m) => {
|
|
115
|
+
if (m.id !== id) return m;
|
|
116
|
+
const next: Marker = { ...m };
|
|
117
|
+
if (change.name !== undefined) {
|
|
118
|
+
const name = change.name.trim().slice(0, MARKER_NAME_MAX);
|
|
119
|
+
if (name) next.name = name;
|
|
120
|
+
else delete next.name;
|
|
121
|
+
}
|
|
122
|
+
if (change.color) next.color = change.color;
|
|
123
|
+
if (change.t !== undefined && Number.isFinite(change.t)) next.t = Math.max(0, Math.round(change.t * 1000) / 1000);
|
|
124
|
+
return next;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function removeMarker(list: readonly Marker[], id: string): Marker[] {
|
|
129
|
+
return list.filter((m) => m.id !== id);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The next marker after `nowMs` (`dir` 1), or the one before (-1); null when there is none that way. */
|
|
133
|
+
export function nextMarker(placed: readonly PlacedMarker[], nowMs: number, dir: 1 | -1): PlacedMarker | null {
|
|
134
|
+
/* within a millisecond is where the playhead already is */
|
|
135
|
+
if (dir > 0) return placed.find((p) => p.ms > nowMs + 1) ?? null;
|
|
136
|
+
for (let i = placed.length - 1; i >= 0; i--) if (placed[i]!.ms < nowMs - 1) return placed[i]!;
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* What M marks: the selected clip under the playhead (the first), else nothing (a marker on the film). A locked
|
|
142
|
+
* track's clips take markers too: a marker changes nothing in the film.
|
|
143
|
+
*/
|
|
144
|
+
export function markerClip(tracks: readonly TimelineTrack[], selectedClipIds: readonly string[], atMs: number): TimelineBlock | null {
|
|
145
|
+
if (!selectedClipIds.length) return null;
|
|
146
|
+
for (const tr of tracks) {
|
|
147
|
+
for (const b of tr.blocks) {
|
|
148
|
+
if (b.clipId && b.loc && selectedClipIds.includes(b.clipId) && atMs >= b.startMs && atMs < b.endMs) return b;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The media pane's top bar at the pane's width, worked out from what its words measure in the person's language and
|
|
3
|
+
* font. The search field takes whatever room is left, so nothing is left empty. As the pane narrows, Upload loses its
|
|
4
|
+
* word first, then the field gets shorter (its hint cut, or gone, down to its icon), and only when even that does not
|
|
5
|
+
* fit does the search become a button. A field too short to type in opens over the bar when clicked. The square
|
|
6
|
+
* buttons (view, filter, sort, new folder, close) always stay.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** What the words measure, in px, at the bar's type. */
|
|
10
|
+
export interface BarWords {
|
|
11
|
+
/** The search's hint ("Search"). */
|
|
12
|
+
hint: number;
|
|
13
|
+
/** Upload's word. */
|
|
14
|
+
upload: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BarLayout {
|
|
18
|
+
/** The search as a field (else a button). */
|
|
19
|
+
field: boolean;
|
|
20
|
+
/** Upload with its word (else its icon). */
|
|
21
|
+
uploadWord: boolean;
|
|
22
|
+
/** Tighter padding and gaps: the narrowest form. */
|
|
23
|
+
tight: boolean;
|
|
24
|
+
/** The field is too short to type in: clicked, it opens over the bar. */
|
|
25
|
+
cramped: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A square button. */
|
|
29
|
+
export const BAR_BUTTON = 28;
|
|
30
|
+
/* the search field around its hint: padding 8 + icon 13 + gap 6 + padding 8, and room for the caret */
|
|
31
|
+
const FIELD_CHROME = 39;
|
|
32
|
+
/** The shortest field: its icon. */
|
|
33
|
+
export const MIN_FIELD = 44;
|
|
34
|
+
/** The shortest field to type in where it is: its icon and a few letters. */
|
|
35
|
+
export const TYPING_FIELD = 72;
|
|
36
|
+
/* the clear button, while something is typed */
|
|
37
|
+
const CLEAR = 18;
|
|
38
|
+
/* Upload around its word: padding 10 + icon 13 + gap 6 + padding 10 */
|
|
39
|
+
const UPLOAD_CHROME = 39;
|
|
40
|
+
|
|
41
|
+
/** The bar for `width` px, with `buttons` square buttons after Upload; `typed`: the search has text. */
|
|
42
|
+
export function barLayout(width: number, words: BarWords, buttons: number, typed: boolean): BarLayout {
|
|
43
|
+
const clear = typed ? CLEAR : 0;
|
|
44
|
+
/* Upload keeps its word while the field still shows its whole hint beside it */
|
|
45
|
+
if (rest(width, false, UPLOAD_CHROME + words.upload, buttons) >= FIELD_CHROME + words.hint + clear) {
|
|
46
|
+
return { field: true, uploadWord: true, tight: false, cramped: false };
|
|
47
|
+
}
|
|
48
|
+
const room = rest(width, false, BAR_BUTTON, buttons);
|
|
49
|
+
if (room >= MIN_FIELD) return { field: true, uploadWord: false, tight: false, cramped: room < TYPING_FIELD + clear };
|
|
50
|
+
return { field: false, uploadWord: false, tight: true, cramped: true };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The room the search field gets: the width less padding, Upload, the square buttons and the gaps between. */
|
|
54
|
+
export function rest(width: number, tight: boolean, upload: number, buttons: number): number {
|
|
55
|
+
const pad = tight ? 16 : 20;
|
|
56
|
+
const gap = tight ? 2 : 4;
|
|
57
|
+
return width - pad - upload - buttons * BAR_BUTTON - (buttons + 1) * gap;
|
|
58
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The media pane's folders (MediaPool): those directly in the folder shown, each with how many files it holds (at any
|
|
3
|
+
* depth) and the four on its cover (pictures first, they say the most; sounds after; by name). Worked out in one pass
|
|
4
|
+
* over the files, with one collator: a project holds thousands of files, the pane is laid out again whenever its
|
|
5
|
+
* listing changes, and a filter and a sort per folder (a collator made for each comparison) took tens of ms each time.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Names in their natural order (shot 2 before shot 10). */
|
|
9
|
+
export const byName = new Intl.Collator(undefined, { numeric: true }).compare;
|
|
10
|
+
|
|
11
|
+
type File = { path: string; name: string; kind: string };
|
|
12
|
+
export type FolderFace<F extends File> = { rel: string; name: string; count: number; covers: F[] };
|
|
13
|
+
|
|
14
|
+
/** Pictures before sounds, then by name: the order of a folder's cover. */
|
|
15
|
+
const coverOrder = (a: File, b: File) => Number(a.kind === 'audio') - Number(b.kind === 'audio') || byName(a.name, b.name);
|
|
16
|
+
const COVERS = 4;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The folders of `dirs` (paths under the media folder) directly in `at` ('' its top), in name order. `relOf` gives a
|
|
20
|
+
* file's path under the media folder, or null for one outside it (a project's own page).
|
|
21
|
+
*/
|
|
22
|
+
export function folderFaces<F extends File>(files: readonly F[], dirs: readonly string[], at: string, relOf: (path: string) => string | null): FolderFace<F>[] {
|
|
23
|
+
const parentOf = (rel: string) => (rel.includes('/') ? rel.slice(0, rel.lastIndexOf('/')) : '');
|
|
24
|
+
const faces = new Map<string, FolderFace<F>>();
|
|
25
|
+
for (const rel of [...dirs].filter((d) => parentOf(d) === at).sort(byName)) faces.set(rel, { rel, name: rel.split('/').pop()!, count: 0, covers: [] });
|
|
26
|
+
if (!faces.size) return [];
|
|
27
|
+
const prefix = at ? `${at}/` : '';
|
|
28
|
+
for (const f of files) {
|
|
29
|
+
const rel = relOf(f.path);
|
|
30
|
+
if (rel == null || !rel.startsWith(prefix)) continue;
|
|
31
|
+
const slash = rel.indexOf('/', prefix.length);
|
|
32
|
+
if (slash < 0) continue; // a file of `at` itself
|
|
33
|
+
const face = faces.get(rel.slice(0, slash));
|
|
34
|
+
if (!face) continue;
|
|
35
|
+
face.count++;
|
|
36
|
+
/* the cover's four, kept in order as they come (no sort of the whole folder) */
|
|
37
|
+
const covers = face.covers;
|
|
38
|
+
if (covers.length === COVERS && coverOrder(f, covers[COVERS - 1]!) >= 0) continue;
|
|
39
|
+
let i = covers.length;
|
|
40
|
+
while (i > 0 && coverOrder(f, covers[i - 1]!) < 0) i--;
|
|
41
|
+
covers.splice(i, 0, f);
|
|
42
|
+
if (covers.length > COVERS) covers.pop();
|
|
43
|
+
}
|
|
44
|
+
return [...faces.values()];
|
|
45
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A file from the media pane put on the timeline without dragging it: its "+" button and Enter, and `,` / `.` from the
|
|
3
|
+
* source viewer (insert / overwrite). The part of the file marked in the source viewer (I and O
|
|
4
|
+
* there), else all of it, as one clip — a video carries its own sound.
|
|
5
|
+
*
|
|
6
|
+
* Where it goes: at the playhead on the targeted track (the track last clicked on the timeline), or without one at
|
|
7
|
+
* the end of the main track (V1) for a picture, of a sound track for a sound (the first with room, else a new one
|
|
8
|
+
* under the others). How, without `,` / `.`: on the main track with the magnet it goes in between at the playhead (as a
|
|
9
|
+
* clip dragged there does); anywhere else it is put down, never cutting what is there — a taken spot opens a track
|
|
10
|
+
* beside it.
|
|
11
|
+
*/
|
|
12
|
+
import type { Op } from '../api.ts';
|
|
13
|
+
import { assetClipElement, assetClipKindOf, dropDurMsOf, type StageSize } from './asset-timeline.ts';
|
|
14
|
+
import type { ResourceDragItem } from './resource-drag';
|
|
15
|
+
import { newTrackSide } from './timeline-drag.ts';
|
|
16
|
+
import { insertPointOn, magnetOn, planRoom, soundTrackFor, TOUCH_MS, type EditMode, type EditModel, type Switches, type TrackDest } from './timeline-edit.ts';
|
|
17
|
+
import { snapToFrame } from './timecode.ts';
|
|
18
|
+
|
|
19
|
+
export type PlaceFile = Pick<ResourceDragItem, 'path' | 'kind' | 'durationMs' | 'endless' | 'reflows' | 'w' | 'h'>;
|
|
20
|
+
|
|
21
|
+
/** A part of the file, in its own ms (the source viewer's in and out). */
|
|
22
|
+
export type SourceRange = { inMs: number; outMs: number };
|
|
23
|
+
|
|
24
|
+
export interface PlaceSpec extends Partial<Switches> {
|
|
25
|
+
file: PlaceFile;
|
|
26
|
+
range?: SourceRange | null;
|
|
27
|
+
/** The targeted track (film.html index), or null for the lowest picture track. */
|
|
28
|
+
target: number | null;
|
|
29
|
+
/** Where: the playhead (ms), or `end`, after the last clip of the track it goes on. */
|
|
30
|
+
at: number | 'end';
|
|
31
|
+
/** Insert or overwrite, asked for (the source viewer's keys); not given, as the timeline's switches say. */
|
|
32
|
+
mode?: EditMode;
|
|
33
|
+
/** The canvas: a picture of another shape gets a box in its own (asset-timeline assetFitBox). */
|
|
34
|
+
stage?: StageSize | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type PlacePlan = { ops: Op[]; track: TrackDest; startMs: number; durMs: number } | { error: 'not-clip' | 'locked' };
|
|
38
|
+
|
|
39
|
+
const isSoundTrack = (role: string | undefined) => role === 'audio';
|
|
40
|
+
|
|
41
|
+
/** The clip as film.html takes it: the file, trimmed to the part marked (a still keeps its length). */
|
|
42
|
+
function elementOf(file: PlaceFile, range: SourceRange | null | undefined, stage: StageSize | null | undefined): { element: Record<string, unknown>; durMs: number } | null {
|
|
43
|
+
const element = assetClipElement(file, stage);
|
|
44
|
+
if (!element) return null;
|
|
45
|
+
const still = Array.isArray(element.time);
|
|
46
|
+
if (range && range.outMs > range.inMs && !(still && assetClipKindOf(file) === 'video')) {
|
|
47
|
+
return { element: { ...element, time: [Math.round(range.inMs) / 1000, Math.round(range.outMs) / 1000] }, durMs: Math.round(range.outMs) - Math.round(range.inMs) };
|
|
48
|
+
}
|
|
49
|
+
return { element, durMs: dropDurMsOf(file) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function placeMedia(model: EditModel, spec: PlaceSpec): PlacePlan {
|
|
53
|
+
const kind = assetClipKindOf(spec.file);
|
|
54
|
+
const made = kind ? elementOf(spec.file, spec.range, spec.stage) : null;
|
|
55
|
+
if (!kind || !made) return { error: 'not-clip' };
|
|
56
|
+
const sound = kind === 'audio';
|
|
57
|
+
const fits = (role: string | undefined) => (sound ? isSoundTrack(role) : !isSoundTrack(role));
|
|
58
|
+
const targeted = spec.target != null ? model.find((t) => t.index === spec.target) : undefined;
|
|
59
|
+
/* a targeted track of the other family (a sound aimed at a picture track) is passed over for the usual one */
|
|
60
|
+
if (targeted && fits(targeted.role) && targeted.locked) return { error: 'locked' };
|
|
61
|
+
const endOf = (index: number) => Math.max(0, ...(model.find((t) => t.index === index)?.clips ?? []).map((c) => c.endMs));
|
|
62
|
+
let track: TrackDest;
|
|
63
|
+
/* a sound track picked for having room: nothing there to push on or cut */
|
|
64
|
+
let roomy = false;
|
|
65
|
+
if (targeted && fits(targeted.role)) {
|
|
66
|
+
track = targeted.index;
|
|
67
|
+
} else if (sound) {
|
|
68
|
+
const sounds = model.filter((t) => isSoundTrack(t.role) && !t.locked);
|
|
69
|
+
if (spec.at === 'end' && sounds.length) {
|
|
70
|
+
/* at the end of a sound track: the first with any sound, or the first */
|
|
71
|
+
track = (sounds.find((t) => t.clips.length) ?? sounds[0]!).index;
|
|
72
|
+
} else {
|
|
73
|
+
const from = spec.at === 'end' ? 0 : snapToFrame(spec.at);
|
|
74
|
+
track = soundTrackFor(model, { startMs: from, endMs: from + made.durMs }, -1);
|
|
75
|
+
roomy = true;
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
/* the main track: the lowest picture track (V1), film.html's last picture track */
|
|
79
|
+
const pictures = model.filter((t) => !isSoundTrack(t.role));
|
|
80
|
+
const lowest = pictures.at(-1);
|
|
81
|
+
if (lowest?.locked) return { error: 'locked' };
|
|
82
|
+
track = lowest ? lowest.index : { insert: 0 };
|
|
83
|
+
}
|
|
84
|
+
let startMs = spec.at === 'end' ? (typeof track === 'number' ? endOf(track) : 0) : Math.max(0, snapToFrame(spec.at));
|
|
85
|
+
if (typeof track !== 'number') return { ops: [{ op: 'insert', clip: made.element as never, at: Math.round(startMs) / 1000, track }], track, startMs, durMs: made.durMs };
|
|
86
|
+
const switches = { magnet: Boolean(spec.magnet), linkage: Boolean(spec.linkage) };
|
|
87
|
+
/* on the main track with the magnet: in between, where the playhead is (before or after the clip under it) */
|
|
88
|
+
const mode: EditMode | 'place' = spec.mode ?? (magnetOn(model, track, switches.magnet) ? 'insert' : 'place');
|
|
89
|
+
if (!spec.mode && mode === 'insert') startMs = insertPointOn(model, track, startMs, startMs);
|
|
90
|
+
const clips = model.find((t) => t.index === track)?.clips ?? [];
|
|
91
|
+
if (mode === 'place' && clips.some((c) => c.startMs < startMs + made.durMs - TOUCH_MS && c.endMs > startMs + TOUCH_MS)) {
|
|
92
|
+
/* the time is taken: a new track beside, nothing cut */
|
|
93
|
+
const role = model.find((t) => t.index === track)?.role;
|
|
94
|
+
const beside: TrackDest = { insert: newTrackSide(track, role) };
|
|
95
|
+
return { ops: [{ op: 'insert', clip: made.element as never, at: Math.round(startMs) / 1000, track: beside }], track: beside, startMs, durMs: made.durMs };
|
|
96
|
+
}
|
|
97
|
+
const insert: Op = { op: 'insert', clip: made.element as never, at: Math.round(startMs) / 1000, track };
|
|
98
|
+
/* at the very end nothing follows: no room to make */
|
|
99
|
+
const after = clips.some((c) => c.endMs > startMs + TOUCH_MS);
|
|
100
|
+
const room = after && !roomy && mode !== 'place' ? planRoom(model, [{ track, startMs, endMs: startMs + made.durMs }], { mode, ...switches }).ops : [];
|
|
101
|
+
return { ops: [insert, ...room], track, startMs, durMs: made.durMs };
|
|
102
|
+
}
|