├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── tests.yml ├── .gitignore ├── .nvmrc ├── Dockerfile ├── LICENSE.md ├── README.md ├── bun.lockb ├── deploy.sh ├── documentation ├── RELEASE.md ├── design │ └── README.md ├── diagrams │ ├── architecture-draft.excalidraw │ └── architecture-draft.png ├── screenshots │ └── 20240617.jpg └── videos │ ├── 20240826.gif │ └── 20240826.mp4 ├── package.json └── packages ├── app ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .nvmrc ├── .prettierrc.json ├── LICENSE.txt ├── components.json ├── forge.config.js ├── main.js ├── next.config.js ├── package.json ├── playwright.config.ts ├── postcss.config.js ├── public │ ├── .gitattributes │ ├── carriers │ │ └── README.md │ ├── datasets │ │ └── baby-names-us-year-of-birth-full.csv │ ├── images │ │ ├── icons │ │ │ ├── interface │ │ │ │ ├── character.svg │ │ │ │ ├── characters.svg │ │ │ │ ├── cloud.svg │ │ │ │ ├── community.svg │ │ │ │ ├── computer.svg │ │ │ │ ├── cut.svg │ │ │ │ ├── downloads.svg │ │ │ │ ├── film.svg │ │ │ │ ├── hidden.svg │ │ │ │ ├── image-to-image.svg │ │ │ │ ├── image.svg │ │ │ │ ├── imagefile.svg │ │ │ │ ├── interpolate.svg │ │ │ │ ├── location.svg │ │ │ │ ├── lora.svg │ │ │ │ ├── misc.svg │ │ │ │ ├── mute.svg │ │ │ │ ├── project.svg │ │ │ │ ├── prompt.svg │ │ │ │ ├── screenplay.svg │ │ │ │ ├── sound.svg │ │ │ │ ├── soundfile.svg │ │ │ │ ├── speech.svg │ │ │ │ ├── team.svg │ │ │ │ ├── text-to-music.svg │ │ │ │ ├── text-to-video.svg │ │ │ │ ├── textfile.svg │ │ │ │ ├── transfer.svg │ │ │ │ ├── transition.svg │ │ │ │ ├── unpute.svg │ │ │ │ ├── upscale.svg │ │ │ │ ├── vendor.svg │ │ │ │ ├── video-folder.svg │ │ │ │ ├── video-to-video.svg │ │ │ │ ├── videofile.svg │ │ │ │ └── visible.svg │ │ │ └── segments │ │ │ │ ├── action.svg │ │ │ │ ├── camera.svg │ │ │ │ ├── character.svg │ │ │ │ ├── depth.svg │ │ │ │ ├── dialogue.svg │ │ │ │ ├── effect.svg │ │ │ │ ├── era.svg │ │ │ │ ├── event.svg │ │ │ │ ├── generic.svg │ │ │ │ ├── interface.svg │ │ │ │ ├── lighting.svg │ │ │ │ ├── location.svg │ │ │ │ ├── mesh.svg │ │ │ │ ├── music.svg │ │ │ │ ├── phenomenon.svg │ │ │ │ ├── sound.svg │ │ │ │ ├── splat.svg │ │ │ │ ├── storyboard.svg │ │ │ │ ├── style.svg │ │ │ │ ├── time.svg │ │ │ │ ├── transition.svg │ │ │ │ ├── video.svg │ │ │ │ └── weather.svg │ │ ├── logos │ │ │ ├── CL.icns │ │ │ ├── CL.iconset │ │ │ │ ├── icon_128x128.png │ │ │ │ ├── icon_128x128@2x.png │ │ │ │ ├── icon_16x16.png │ │ │ │ ├── icon_16x16@2x.png │ │ │ │ ├── icon_256x256.png │ │ │ │ ├── icon_256x256@2x.png │ │ │ │ ├── icon_32x32.png │ │ │ │ ├── icon_32x32@2x.png │ │ │ │ ├── icon_512x512.png │ │ │ │ └── icon_512x512@2x.png │ │ │ ├── CL.png │ │ │ ├── clapper.png │ │ │ ├── logo-desaturated.png │ │ │ ├── logo-discord-2.png │ │ │ ├── logo-discord-3.png │ │ │ ├── logo-no-bg.png │ │ │ ├── logo-v2.png │ │ │ └── logo-v2.xcf │ │ ├── onboarding │ │ │ ├── get-started.png │ │ │ ├── get-started.xcf │ │ │ ├── pick-an-example.png │ │ │ └── pick-an-example.xcf │ │ └── providers │ │ │ ├── anthropic.png │ │ │ ├── bigmodel.jpeg │ │ │ ├── civitai.png │ │ │ ├── cohere.png │ │ │ ├── comfyicu.png │ │ │ ├── comfyui.png │ │ │ ├── elevenlabs.png │ │ │ ├── everartai.png │ │ │ ├── falai.png │ │ │ ├── fireworks.png │ │ │ ├── google.png │ │ │ ├── groq.png │ │ │ ├── hedra.png │ │ │ ├── hotshot.png │ │ │ ├── huggingface.png │ │ │ ├── kitsai.png │ │ │ ├── kuaishou.png │ │ │ ├── leonardoai.png │ │ │ ├── letzai.png │ │ │ ├── lumalabs.png │ │ │ ├── midjourney.png │ │ │ ├── mistralai.png │ │ │ ├── modelslab.jpeg │ │ │ ├── none.png │ │ │ ├── openai.png │ │ │ ├── piapi.jpg │ │ │ ├── replicate.jpeg │ │ │ ├── runwayml.png │ │ │ ├── stabilityai.png │ │ │ ├── suno.png │ │ │ └── udio.png │ ├── samples │ │ ├── claps │ │ │ ├── Afterglow v10 X Rewrite Bryan E. Harris 2023.clap │ │ │ ├── empty_project.clap │ │ │ ├── empty_project.yaml │ │ │ ├── test.clap │ │ │ └── wasteland.clap │ │ └── scripts │ │ │ ├── Citizen Kane.txt │ │ │ ├── DISCLAIMER.md │ │ │ ├── Raiders of the Lost Ark.txt │ │ │ ├── Rear Window.txt │ │ │ ├── Star Wars A New Hope.txt │ │ │ ├── The Apery.txt │ │ │ ├── The Goonies.txt │ │ │ └── The Wizard Of Oz.txt │ ├── voices │ │ └── README.md │ ├── wasm │ │ └── MediaInfoModule.wasm │ ├── workers │ │ └── captioning.worker.js │ └── workflows │ │ └── comfyui │ │ └── default_comfyui_workflow_api.json ├── src │ ├── app │ │ ├── api │ │ │ ├── assistant │ │ │ │ ├── askAnyAssistant.ts │ │ │ │ ├── parseLangChainResponse.ts │ │ │ │ ├── parser.ts │ │ │ │ ├── providers │ │ │ │ │ └── google │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ └── sample.txt │ │ │ │ ├── route.ts │ │ │ │ ├── samples.ts │ │ │ │ └── templates.ts │ │ │ ├── getApiKey.ts │ │ │ ├── globalSettings.ts │ │ │ └── resolve │ │ │ │ ├── providers │ │ │ │ ├── aitube │ │ │ │ │ ├── README.md │ │ │ │ │ └── index.ts │ │ │ │ ├── bigmodel │ │ │ │ │ ├── callCogVideoX.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── civitai │ │ │ │ │ └── index.ts │ │ │ │ ├── comfy-comfydeploy │ │ │ │ │ └── index.ts │ │ │ │ ├── comfy-comfyicu │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── comfy-huggingface │ │ │ │ │ └── index.ts │ │ │ │ ├── comfy-replicate │ │ │ │ │ ├── index.ts │ │ │ │ │ └── runWorkflow.ts │ │ │ │ ├── comfyui │ │ │ │ │ ├── convertComfyUiWorkflowApiToClapWorkflow.ts │ │ │ │ │ ├── createPromptBuilder.spec.ts │ │ │ │ │ ├── createPromptBuilder.ts │ │ │ │ │ ├── getInputsFromComfyUiWorkflow.ts │ │ │ │ │ ├── getMainInputIdsByClapWorkflowCategory.ts │ │ │ │ │ ├── getMainInputsFromComfyUiWorkflow.ts │ │ │ │ │ ├── graph.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── tests.spec.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── falai │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── runFaceSwap.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── getWorkflowInputValues.ts │ │ │ │ ├── gradio │ │ │ │ │ └── index.ts │ │ │ │ ├── hotshot │ │ │ │ │ ├── createHotshotVideo.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── huggingface │ │ │ │ │ ├── generateImage.ts │ │ │ │ │ ├── generateMusic.ts │ │ │ │ │ ├── generateVideo.ts │ │ │ │ │ ├── generateVoice.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── letzai │ │ │ │ │ ├── callCreateImage.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── lumalabs │ │ │ │ │ └── index.ts │ │ │ │ ├── modelslab │ │ │ │ │ └── index.ts │ │ │ │ ├── openai │ │ │ │ │ └── TODO.md │ │ │ │ ├── piapi │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── kling │ │ │ │ │ │ ├── createAndFetchKlingVideo.ts │ │ │ │ │ │ ├── createKlingVideo.ts │ │ │ │ │ │ ├── fetchKlingVideoResult.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── lumalabs │ │ │ │ │ │ ├── createAndFetchDreamMachineVideo.ts │ │ │ │ │ │ ├── createDreamMachineVideo.ts │ │ │ │ │ │ ├── fetchDreamMachineVideoResult.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── midjourney │ │ │ │ │ │ ├── createImage.ts │ │ │ │ │ │ ├── createMidjourneyImage.ts │ │ │ │ │ │ ├── fetchMidjourneyResult.ts │ │ │ │ │ │ └── types.ts │ │ │ │ ├── replicate │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── runFaceSwap.ts │ │ │ │ │ └── runLipSync.ts │ │ │ │ └── stabilityai │ │ │ │ │ ├── generateImage.ts │ │ │ │ │ ├── generateVideo.ts │ │ │ │ │ └── index.ts │ │ │ │ └── route.ts │ │ ├── cute_1024x1024x32.png │ │ ├── cute_favicon.ico │ │ ├── cute_icon.png │ │ ├── embed │ │ │ ├── EmbeddedPlayer.tsx │ │ │ ├── README.md │ │ │ ├── StaticOrInteractive.tsx │ │ │ ├── TogglePlayback.tsx │ │ │ ├── page.tsx │ │ │ ├── useParentController.ts │ │ │ └── useSetupIframeOnce.ts │ │ ├── favicon.ico │ │ ├── favicon_square.ico │ │ ├── fonts │ │ │ ├── alarm-clock │ │ │ │ └── alarm-clock.woff2 │ │ │ └── index.ts │ │ ├── icon_NEXTJS_BUG.png │ │ ├── icon_square.png │ │ ├── layout.tsx │ │ ├── main.tsx │ │ ├── page.tsx │ │ └── styles │ │ │ ├── globals.css │ │ │ ├── react-reflex-custom.css │ │ │ └── react-reflex.css │ ├── components │ │ ├── assistant │ │ │ ├── ChatBubble.tsx │ │ │ └── ChatView.tsx │ │ ├── core │ │ │ ├── providers │ │ │ │ ├── ClapWorkflowProviderLogo.tsx │ │ │ │ ├── ClapWorkflowProviderName.tsx │ │ │ │ ├── index.ts │ │ │ │ └── logos.ts │ │ │ ├── timeline │ │ │ │ ├── Slider.tsx │ │ │ │ ├── TimelineZoom.tsx │ │ │ │ └── index.tsx │ │ │ ├── tree │ │ │ │ ├── README.md │ │ │ │ ├── chainable-map.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── root.tsx │ │ │ │ ├── roving.tsx │ │ │ │ ├── tree-state.ts │ │ │ │ ├── types.ts │ │ │ │ └── useTreeNode.ts │ │ │ └── waveform │ │ │ │ ├── types.ts │ │ │ │ └── useWaveform.ts │ │ ├── dialogs │ │ │ ├── iframe-warning │ │ │ │ └── index.tsx │ │ │ └── loader │ │ │ │ ├── LoadingDialog.tsx │ │ │ │ └── index.tsx │ │ ├── editors │ │ │ ├── Editors.tsx │ │ │ ├── EntityEditor │ │ │ │ ├── EntityTree │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── useEntityTree.ts │ │ │ │ ├── EntityViewer │ │ │ │ │ ├── EntityList.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── FilterEditor │ │ │ │ ├── FilterTree │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── useFilterTree.ts │ │ │ │ ├── FilterViewer │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── ProjectEditor │ │ │ │ ├── index.tsx │ │ │ │ └── useSyncProjectEditor.ts │ │ │ ├── ScriptEditor │ │ │ │ ├── README.md │ │ │ │ ├── index.tsx │ │ │ │ └── styles.css │ │ │ ├── SegmentEditor │ │ │ │ └── index.tsx │ │ │ └── WorkflowEditor │ │ │ │ ├── WorkflowTree │ │ │ │ ├── index.tsx │ │ │ │ └── useWorkflowTree.ts │ │ │ │ ├── WorkflowViewer │ │ │ │ ├── ReactFlowCanvas │ │ │ │ │ ├── NodeView.tsx │ │ │ │ │ ├── clapWorkflowToReactWorkflow.ts │ │ │ │ │ ├── formats │ │ │ │ │ │ ├── comfyui │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ ├── falai │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ └── glif │ │ │ │ │ │ │ ├── glifToReactWorkflow.ts │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── samples │ │ │ │ │ │ ├── comfyicu.ts │ │ │ │ │ │ └── glif.ts │ │ │ │ │ └── types.ts │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ ├── forms │ │ │ ├── FormArea.tsx │ │ │ ├── FormComfyUIWorkflowSettings.tsx │ │ │ ├── FormDir.tsx │ │ │ ├── FormField.tsx │ │ │ ├── FormFile.tsx │ │ │ ├── FormInput.tsx │ │ │ ├── FormLabel.tsx │ │ │ ├── FormRadio.tsx │ │ │ ├── FormSection.tsx │ │ │ ├── FormSelect.tsx │ │ │ ├── FormSlider.tsx │ │ │ ├── FormSwitch.tsx │ │ │ └── index.ts │ │ ├── icons │ │ │ ├── getAppropriateIcon.ts │ │ │ └── index.tsx │ │ ├── mobile │ │ │ └── MobileMenu.tsx │ │ ├── monitor │ │ │ ├── Counter │ │ │ │ └── index.tsx │ │ │ ├── DynamicPlayer │ │ │ │ ├── DynamicBuffer.tsx │ │ │ │ ├── StoryboardBuffer.tsx │ │ │ │ ├── VideoClipBuffer.tsx │ │ │ │ └── index.tsx │ │ │ ├── PlayerControls │ │ │ │ └── index.tsx │ │ │ ├── README.md │ │ │ ├── Separator │ │ │ │ └── index.tsx │ │ │ ├── StaticPlayer │ │ │ │ └── index.tsx │ │ │ ├── UniversalPlayer │ │ │ │ └── index.tsx │ │ │ ├── VUMeter │ │ │ │ └── index.tsx │ │ │ ├── icons │ │ │ │ ├── icon-switch.tsx │ │ │ │ └── single-icon.tsx │ │ │ ├── index.tsx │ │ │ └── utils │ │ │ │ ├── splitElapsedTime.tsx │ │ │ │ └── zeroPad.ts │ │ ├── settings │ │ │ ├── assistant.tsx │ │ │ ├── constants.ts │ │ │ ├── editors.tsx │ │ │ ├── image.tsx │ │ │ ├── index.tsx │ │ │ ├── music.tsx │ │ │ ├── provider.tsx │ │ │ ├── sound.tsx │ │ │ ├── video.tsx │ │ │ └── voice.tsx │ │ ├── tags │ │ │ ├── Tag.tsx │ │ │ ├── colors.ts │ │ │ └── types.ts │ │ ├── tasks │ │ │ ├── TaskStatusUpdate.tsx │ │ │ └── useTasks.tsx │ │ ├── toolbars │ │ │ ├── bottom-bar │ │ │ │ ├── BottomMenuItem.tsx │ │ │ │ ├── DesktopBottomBar.tsx │ │ │ │ ├── MobileBottomBar.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── metrics │ │ │ │ │ └── index.tsx │ │ │ │ └── tasks │ │ │ │ │ └── index.tsx │ │ │ ├── editors-menu │ │ │ │ ├── EditorsSideMenu.tsx │ │ │ │ ├── EditorsSideMenuItem.tsx │ │ │ │ └── NatureIcon.tsx │ │ │ ├── system-menu │ │ │ │ └── index.tsx │ │ │ ├── top-bar │ │ │ │ └── index.tsx │ │ │ └── top-menu │ │ │ │ ├── ActivitySpinner │ │ │ │ └── index.tsx │ │ │ │ ├── IsBusy │ │ │ │ └── index.tsx │ │ │ │ ├── ToggleFullScreen │ │ │ │ └── index.tsx │ │ │ │ ├── ToggleView │ │ │ │ └── index.tsx │ │ │ │ ├── ToggleWindowLayout │ │ │ │ └── index.tsx │ │ │ │ ├── TopMenuLogo │ │ │ │ └── index.tsx │ │ │ │ ├── assistant │ │ │ │ └── index.tsx │ │ │ │ ├── constants.ts │ │ │ │ ├── edit │ │ │ │ └── index.tsx │ │ │ │ ├── file │ │ │ │ ├── index.tsx │ │ │ │ └── useQueryStringLoader.ts │ │ │ │ ├── image │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── lists │ │ │ │ ├── AssistantWorkflows.tsx │ │ │ │ ├── ImageDepthWorkflows.tsx │ │ │ │ ├── ImageFaceswapWorkflows.tsx │ │ │ │ ├── ImageGenerationWorkflows.tsx │ │ │ │ ├── ImageSegmentationWorkflows.tsx │ │ │ │ ├── ImageUpscalingWorkflows.tsx │ │ │ │ ├── LoraModelList.tsx │ │ │ │ ├── MusicGenerationWorkflows.tsx │ │ │ │ ├── RenderingStrategyList.tsx │ │ │ │ ├── SoundGenerationWorkflows.tsx │ │ │ │ ├── ThemeList.tsx │ │ │ │ ├── VideoDepthWorkflows.tsx │ │ │ │ ├── VideoGenerationWorkflows.tsx │ │ │ │ ├── VideoLipsyncWorkflows.tsx │ │ │ │ ├── VideoSegmentationWorkflows.tsx │ │ │ │ ├── VideoUpscalingWorkflows.tsx │ │ │ │ ├── VoiceGenerationWorkflows.tsx │ │ │ │ ├── formatProvider.ts │ │ │ │ ├── getWorkflowProviders.ts │ │ │ │ └── hasNoPublicAPI.ts │ │ │ │ ├── music │ │ │ │ └── index.tsx │ │ │ │ ├── plugins │ │ │ │ └── index.tsx │ │ │ │ ├── sound │ │ │ │ └── index.tsx │ │ │ │ ├── video │ │ │ │ └── index.tsx │ │ │ │ ├── view │ │ │ │ └── index.tsx │ │ │ │ └── voice │ │ │ │ └── index.tsx │ │ ├── tree-browsers │ │ │ ├── style │ │ │ │ └── treeNodeStyles.ts │ │ │ ├── types.ts │ │ │ └── utils │ │ │ │ ├── getCollectionColor.ts │ │ │ │ ├── getCollectionItemTextColor.ts │ │ │ │ ├── getItemColor.ts │ │ │ │ └── isSomething.ts │ │ ├── ui │ │ │ ├── accordion.tsx │ │ │ ├── alert.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── command.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── menubar-legacy.tsx │ │ │ ├── menubar.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── slider.tsx │ │ │ ├── sonner.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toast.tsx │ │ │ ├── toaster.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── use-toast.ts │ │ │ └── vertical-slider.tsx │ │ ├── uikit │ │ │ └── default │ │ │ │ ├── button.tsx │ │ │ │ └── theme.tsx │ │ ├── windows │ │ │ └── index.tsx │ │ └── wizards │ │ │ └── project │ │ │ └── index.tsx │ ├── experiments │ │ ├── README.md │ │ ├── humanizer │ │ │ ├── drivingVideosByActionEmotionAndIntensity$.ts │ │ │ ├── findRealisticDrivingVideo.ts │ │ │ └── types.ts │ │ ├── moodboard │ │ │ ├── MoodboardView.tsx │ │ │ └── Node.tsx │ │ └── samples │ │ │ └── demo.ts │ ├── lib │ │ ├── core │ │ │ └── constants.ts │ │ ├── ffmpeg │ │ │ ├── convertAudioToMp3.ts │ │ │ └── getMediaInfo.ts │ │ ├── hf │ │ │ ├── adapter │ │ │ │ ├── README.md │ │ │ │ ├── adaptAnyInputsToGradioInputs.ts │ │ │ │ ├── findMainGradioEndpoint.ts │ │ │ │ ├── getAdaptationScore.ts │ │ │ │ ├── getDefaultFields.ts │ │ │ │ └── identifyField.ts │ │ │ ├── callGradioApi.ts │ │ │ ├── cloneSpace.ts │ │ │ ├── getCurrentOwner.ts │ │ │ ├── getGradioApiInfo.ts │ │ │ ├── getSpaceStatus.ts │ │ │ ├── getSpaces.ts │ │ │ ├── parseHuggingFaceHubId.test.ts │ │ │ ├── parseHuggingFaceHubId.ts │ │ │ ├── runSpace.ts │ │ │ ├── types.ts │ │ │ └── useMyGradioSpaces.ts │ │ ├── hooks │ │ │ ├── README.md │ │ │ ├── index.ts │ │ │ ├── useAnimationFrame.ts │ │ │ ├── useBreakpoints.ts │ │ │ ├── useDebounce.ts │ │ │ ├── useDebounceFn.ts │ │ │ ├── useFullscreenStatus.ts │ │ │ ├── useOpenFilePicker.ts │ │ │ ├── usePerformanceMeter.ts │ │ │ ├── useQueryStringParams.ts │ │ │ ├── useRequestAnimationFrame.ts │ │ │ └── useTitle.ts │ │ ├── kdenlive │ │ │ ├── README.md │ │ │ ├── entry.ts │ │ │ ├── index.ts │ │ │ ├── kdenlive.ts │ │ │ ├── makeIDGen.ts │ │ │ ├── playlist.ts │ │ │ ├── producer.ts │ │ │ ├── producer_original.ts │ │ │ └── tractor.ts │ │ └── utils │ │ │ ├── addBase64Header.ts │ │ │ ├── base64DataUriToBlob.test.ts │ │ │ ├── base64DataUriToBlob.ts │ │ │ ├── base64DataUriToFile.ts │ │ │ ├── base64DataUriToUint8Array.ts │ │ │ ├── blobToBase64DataUri.ts │ │ │ ├── cn.test.ts │ │ │ ├── cn.ts │ │ │ ├── convertToJpeg.ts │ │ │ ├── debounceAsync.ts │ │ │ ├── debounceSync.ts │ │ │ ├── decodeOutput.ts │ │ │ ├── fetchContentToBase64.ts │ │ │ ├── findClosest.ts │ │ │ ├── formatDuration.test.ts │ │ │ ├── formatDuration.ts │ │ │ ├── formatSegmentForExport.test.ts │ │ │ ├── formatSegmentForExport.ts │ │ │ ├── getTypeAndExtension.test.ts │ │ │ ├── getTypeAndExtension.ts │ │ │ ├── getValidBoolean.ts │ │ │ ├── getValidComfyWorkflowTemplate.test.ts │ │ │ ├── getValidComfyWorkflowTemplate.ts │ │ │ ├── getValidNumber.ts │ │ │ ├── getValidString.ts │ │ │ ├── index.ts │ │ │ ├── isValidNumber.ts │ │ │ ├── parseComfyIcuAccelerator.ts │ │ │ ├── parsePdfToIndentedText.ts │ │ │ └── sleep.ts │ └── services │ │ ├── README.md │ │ ├── api │ │ └── resolve.ts │ │ ├── assistant │ │ ├── askAssistant.ts │ │ ├── getDefaultAssistantState.ts │ │ ├── parseRawInputToAction.ts │ │ ├── updateStoryAndScene.ts │ │ ├── useAssistant.ts │ │ └── useVoiceAssistant.ts │ │ ├── audio │ │ ├── README.md │ │ ├── analyzeAudio.ts │ │ ├── detectBPM.ts │ │ ├── getDefaultAudioState.ts │ │ ├── startAudioSourceNode.ts │ │ └── useAudio.ts │ │ ├── autocomplete │ │ ├── extractCaptionFromFrameMoondream.ts │ │ ├── extractCaptionsFromFrames.ts │ │ ├── getDefaultAutocompleteState.ts │ │ ├── types.ts │ │ └── useAutocomplete.ts │ │ ├── broadcast │ │ ├── constants.ts │ │ ├── getDefaultBroadcastState.ts │ │ └── useBroadcast.ts │ │ ├── debug.ts │ │ ├── editors │ │ ├── entity-editor │ │ │ ├── getDefaultEntityEditorState.ts │ │ │ └── useEntityEditor.ts │ │ ├── filter-editor │ │ │ ├── README.md │ │ │ ├── demo.ts │ │ │ ├── filters │ │ │ │ ├── analogLens.ts │ │ │ │ ├── cinematic.ts │ │ │ │ ├── colorMapping.ts │ │ │ │ ├── colorTemperature.ts │ │ │ │ ├── crossProcessing.ts │ │ │ │ ├── filmDegradation.ts │ │ │ │ ├── index.ts │ │ │ │ ├── infrared.ts │ │ │ │ ├── lomography.ts │ │ │ │ ├── splitToning.ts │ │ │ │ ├── toneMapping.ts │ │ │ │ └── vintageFilm.ts │ │ │ ├── getDefaultFilterEditorState.ts │ │ │ ├── runFilterPipeline.ts │ │ │ └── useFilterEditor.ts │ │ ├── getDefaultEditorsState.ts │ │ ├── index.ts │ │ ├── project-editor │ │ │ ├── getDefaultProjectEditorState.ts │ │ │ └── useProjectEditor.ts │ │ ├── script-editor │ │ │ ├── getDefaultScriptEditorState.ts │ │ │ └── useScriptEditor.ts │ │ ├── segment-editor │ │ │ ├── getDefaultSegmentEditorState.ts │ │ │ └── useSegmentEditor.ts │ │ ├── useEditors.ts │ │ └── workflow-editor │ │ │ ├── getDefaultWorkflowEditorState.ts │ │ │ ├── getSegmentWorkflowProviderAndEngine.ts │ │ │ ├── useDynamicWorkflows.ts │ │ │ ├── useWorkflowEditor.ts │ │ │ └── workflows │ │ │ ├── _documentation_ │ │ │ └── demo_examples.ts │ │ │ ├── aitube │ │ │ └── index.ts │ │ │ ├── anthropic │ │ │ └── index.ts │ │ │ ├── bigmodel │ │ │ └── index.ts │ │ │ ├── civitai │ │ │ └── index.ts │ │ │ ├── cohere │ │ │ └── index.ts │ │ │ ├── comfydeploy │ │ │ └── index.ts │ │ │ ├── comfyicu │ │ │ └── index.ts │ │ │ ├── comfyui │ │ │ ├── getComfyWorkflow.ts │ │ │ └── index.ts │ │ │ ├── common │ │ │ ├── comfyui │ │ │ │ ├── flux_plus_ultimatesdupscale.ts │ │ │ │ └── text_to_image_demo_workflow.ts │ │ │ ├── defaultValues.ts │ │ │ ├── loras │ │ │ │ ├── canWorkflowUseLora.ts │ │ │ │ ├── getWorkflowInputField.ts │ │ │ │ ├── getWorkflowLora.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ │ ├── elevenlabs │ │ │ └── index.ts │ │ │ ├── falai │ │ │ ├── comfyuiWorkflows.ts │ │ │ ├── defaultWorkflows.ts │ │ │ └── index.ts │ │ │ ├── fireworksai │ │ │ └── index.ts │ │ │ ├── google │ │ │ └── index.ts │ │ │ ├── groq │ │ │ └── index.ts │ │ │ ├── hotshot │ │ │ └── index.ts │ │ │ ├── huggingface │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── letzai │ │ │ └── index.ts │ │ │ ├── lumalabs │ │ │ └── index.ts │ │ │ ├── mistralai │ │ │ └── index.ts │ │ │ ├── none │ │ │ └── index.ts │ │ │ ├── openai │ │ │ └── index.ts │ │ │ ├── openart │ │ │ └── samples │ │ │ │ └── openart_workflow.json │ │ │ ├── piapi │ │ │ └── index.ts │ │ │ ├── replicate │ │ │ ├── comfyuiWorkflows.ts │ │ │ ├── defaultWorkflows.ts │ │ │ └── index.ts │ │ │ └── stabilityai │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── inputs │ │ ├── getDefaultInputsState.ts │ │ ├── types.ts │ │ └── useInputs.ts │ │ ├── io │ │ ├── createFullVideo.ts │ │ ├── extractFramesFromVideo.ts │ │ ├── extractScenesFromVideo.ts │ │ ├── ffmpegUtils.ts │ │ ├── fileDataToBase64.ts │ │ ├── formats │ │ │ ├── edl.ts │ │ │ ├── fcp.ts │ │ │ ├── index.ts │ │ │ ├── mlt.ts │ │ │ ├── otio.ts │ │ │ └── otioz.ts │ │ ├── getDefaultIOState.ts │ │ ├── imageCaptioning.ts │ │ ├── parseFileIntoSegments.ts │ │ ├── parseFileName.ts │ │ ├── parseFilesIntoSegments.ts │ │ └── useIO.ts │ │ ├── metrics │ │ ├── constants.ts │ │ ├── getDefaultClapWorkflowProviderMetrics.ts │ │ ├── getDefaultMetricsModelEstimations.ts │ │ ├── getDefaultMetricsModelStats.ts │ │ ├── getDefaultMetricsPerProvider.ts │ │ ├── getDefaultMetricsState.ts │ │ ├── types.ts │ │ └── useMetrics.ts │ │ ├── mic │ │ ├── getDefaultMicState.ts │ │ └── useMic.ts │ │ ├── monitor │ │ ├── README.md │ │ ├── getDefaultMonitorState.ts │ │ └── useMonitor.ts │ │ ├── plugins │ │ ├── constants.ts │ │ ├── fetchAndRun.ts │ │ ├── getDefaultPluginsState.ts │ │ └── usePlugins.ts │ │ ├── renderer │ │ ├── constants.ts │ │ ├── getDefaultBufferedSegments.ts │ │ ├── getDefaultRendererState.ts │ │ ├── getSegmentCacheKey.ts │ │ ├── index.ts │ │ ├── useRenderLoop.ts │ │ └── useRenderer.ts │ │ ├── resolver │ │ ├── constants.ts │ │ ├── getDefaultResolveRequestPrompts.ts │ │ ├── getDefaultResolverState.ts │ │ └── useResolver.ts │ │ ├── settings │ │ ├── getDefaultSettingsState.ts │ │ ├── index.ts │ │ ├── useSettings.ts │ │ └── workflows │ │ │ ├── image.ts │ │ │ ├── parseWorkflow.ts │ │ │ └── video.ts │ │ ├── simulator │ │ ├── useDefaultSimulatorState.ts │ │ └── useSimulator.ts │ │ ├── ui │ │ ├── getDefaultUIState.ts │ │ ├── index.ts │ │ ├── theme.ts │ │ ├── useTheme.ts │ │ └── useUI.ts │ │ └── windows │ │ ├── types.ts │ │ └── useWindows.ts ├── tailwind.config.js ├── tests │ ├── examples.spec.txt │ └── main.spec.ts ├── tsconfig.json └── vitest.config.mts ├── broadway ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── LICENSE.md ├── README.md ├── package.json ├── samples │ ├── claps │ │ └── Afterglow v10 X Rewrite Bryan E. Harris 2023.clap │ └── scripts │ │ ├── Afterglow v10 X Rewrite Bryan E. Harris 2023.txt │ │ └── LICENCE.txt ├── src │ ├── analysis │ │ ├── analyzeLine.ts │ │ ├── analyzeName.ts │ │ ├── analyzeScreenplay.ts │ │ ├── findSubtext.ts │ │ ├── getEntityAnalysisPrompt.ts │ │ ├── getScreenplayFromText.ts │ │ ├── guessAgeAndGender.ts │ │ ├── index.ts │ │ ├── isCharacterLine.ts │ │ ├── isDialogueLine.ts │ │ ├── isPageSeparator.ts │ │ ├── isVoiceOver.ts │ │ ├── loadAgeAndGenderDataset.ts │ │ ├── normalizeName.ts │ │ ├── parseCharacterName.ts │ │ ├── parseDialogueLine.ts │ │ ├── parseScenes.ts │ │ └── parseScriptToClap.ts │ ├── constants │ │ ├── general.ts │ │ ├── index.ts │ │ ├── mocks.ts │ │ └── screenplaySequences.ts │ ├── declarations.d.ts │ ├── factories │ │ ├── createSegment.ts │ │ ├── generateClap.ts │ │ └── index.ts │ ├── index.ts │ ├── parsers │ │ ├── age │ │ │ └── index.ts │ │ ├── costumes │ │ │ └── database.ts │ │ ├── entity │ │ │ └── index.ts │ │ ├── eras │ │ │ ├── database.ts │ │ │ └── index.ts │ │ ├── genres │ │ │ ├── database.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── lights │ │ │ ├── database.ts │ │ │ └── index.ts │ │ ├── locations │ │ │ ├── database.ts │ │ │ ├── index.ts │ │ │ └── locations.ts │ │ ├── music │ │ │ └── database.ts │ │ ├── names │ │ │ └── index.ts │ │ ├── shots │ │ │ ├── database.ts │ │ │ └── index.ts │ │ ├── sounds │ │ │ ├── database.ts │ │ │ └── index.ts │ │ ├── transitions │ │ │ ├── index.ts │ │ │ ├── parseTransition.ts │ │ │ └── transitions.ts │ │ ├── utils │ │ │ ├── createOccurrenceCounter.ts │ │ │ ├── createParser.ts │ │ │ ├── createSimpleParser.ts │ │ │ ├── getParserItemFromLabel.ts │ │ │ └── index.ts │ │ └── weather │ │ │ ├── database.ts │ │ │ └── index.ts │ ├── tests │ │ ├── main.test.ts │ │ └── setup.js │ ├── types.ts │ └── utils │ │ ├── cleanUTF8Characters.ts │ │ ├── deduplicate.ts │ │ ├── getEntities.ts │ │ ├── index.ts │ │ ├── isAllCaps.ts │ │ ├── onlyContainsStrangeNumber.ts │ │ ├── pick.ts │ │ └── sleep.ts ├── tsconfig.json └── tsconfig.types.json ├── clap ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── LICENSE.md ├── README.md ├── package.json ├── src │ ├── constants │ │ ├── defaultValues.ts │ │ └── index.ts │ ├── converters │ │ ├── blobToDataUri.ts │ │ ├── clapToDataUri.ts │ │ ├── dataUriToBlob.ts │ │ └── index.ts │ ├── factories │ │ ├── index.ts │ │ ├── newClap.ts │ │ ├── newEntity.ts │ │ ├── newSegment.ts │ │ └── newWorkflow.ts │ ├── helpers │ │ ├── README.md │ │ ├── buildEntityIndex.ts │ │ ├── filterAssets.ts │ │ ├── filterSegmentsByCategory.ts │ │ ├── generateClapFromSimpleStory.ts │ │ ├── getEmptyClap.ts │ │ ├── index.ts │ │ └── removeGeneratedAssetUrls.ts │ ├── index.ts │ ├── io │ │ ├── fetchClap.ts │ │ ├── index.ts │ │ ├── parseClap.ts │ │ ├── serializeClap.ts │ │ └── updateClap.ts │ ├── samples │ │ └── stories.ts │ ├── sanitizers │ │ ├── index.ts │ │ ├── sanitizeEntities.ts │ │ ├── sanitizeEntity.ts │ │ ├── sanitizeMeta.ts │ │ ├── sanitizeSegment.ts │ │ ├── sanitizeSegments.ts │ │ ├── sanitizeWorkflow.ts │ │ └── sanitizeWorkflows.ts │ ├── types.ts │ └── utils │ │ ├── filterSegments.ts │ │ ├── filterSegmentsWithinRange.ts │ │ ├── generateSeed.ts │ │ ├── getClapAssetSourceType.ts │ │ ├── getValidNumber.ts │ │ ├── index.ts │ │ ├── isValidNumber.ts │ │ ├── parseImageRatio.ts │ │ ├── parseOutputType.ts │ │ ├── parseSegmentCategory.ts │ │ ├── parseSegmentStatus.ts │ │ ├── parseWorkflowCategory.ts │ │ ├── parseWorkflowEngine.ts │ │ ├── parseWorkflowProvider.ts │ │ └── uuid.ts ├── tsconfig.json └── tsconfig.types.json ├── clapper-services ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── LICENSE.md ├── README.md ├── package.json ├── src │ ├── assistant.ts │ ├── audio.ts │ ├── base-types.ts │ ├── broadcast.ts │ ├── editors.ts │ ├── entity-editor.ts │ ├── filter-editor.ts │ ├── index.ts │ ├── io.ts │ ├── mic.ts │ ├── monitor.ts │ ├── plugin.ts │ ├── plugins.ts │ ├── project-editor.ts │ ├── renderer.ts │ ├── resolver.ts │ ├── script-editor.ts │ ├── segment-editor.ts │ ├── services.ts │ ├── settings.ts │ ├── simulator.ts │ ├── speech.d.ts │ ├── tasks.ts │ ├── types.ts │ ├── ui.ts │ ├── version-control.ts │ └── workflow-editor.ts ├── tsconfig.json └── tsconfig.types.json ├── client ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── LICENSE.md ├── README.md ├── package.json ├── src │ ├── api │ │ ├── createClap.ts │ │ ├── editClapDialogues.ts │ │ ├── editClapEntities.ts │ │ ├── editClapMusic.ts │ │ ├── editClapSounds.ts │ │ ├── editClapStory.ts │ │ ├── editClapStoryboards.ts │ │ ├── editClapVideos.ts │ │ ├── exportClapToVideo.ts │ │ └── index.ts │ ├── constants │ │ ├── config.ts │ │ ├── defaultValues.ts │ │ ├── index.ts │ │ └── types.ts │ ├── index.ts │ ├── parsers │ │ ├── index.ts │ │ ├── parseEntityPrompt.ts │ │ ├── parseString.ts │ │ └── parseStringArray.ts │ └── utils │ │ ├── applyClapCompletion.ts │ │ └── index.ts ├── tsconfig.json └── tsconfig.types.json ├── colors ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── LICENSE.md ├── README.md ├── package.json ├── src │ ├── constants │ │ ├── colors.ts │ │ ├── index.ts │ │ └── segments.ts │ ├── index.ts │ └── types.ts ├── tsconfig.json └── tsconfig.types.json ├── engine ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── LICENSE.md ├── README.md ├── package.json ├── src │ ├── index.ts │ ├── prompts │ │ ├── getBackgroundAudioPrompt.ts │ │ ├── getCharacterPrompt.ts │ │ ├── getCharacterReferencePrompt.ts │ │ ├── getMusicPrompt.ts │ │ ├── getPositivePrompt.ts │ │ ├── getSoundPrompt.ts │ │ ├── getSpeechBackgroundAudioPrompt.ts │ │ ├── getSpeechForegroundAudioPrompt.ts │ │ ├── getVideoPrompt.ts │ │ ├── index.ts │ │ └── priorities.ts │ ├── renderers │ │ ├── index.ts │ │ ├── storyboard │ │ │ └── renderShotToStoryboard.ts │ │ └── video │ │ │ └── renderShotToVideo.ts │ ├── types.ts │ └── utils │ │ ├── deduplicate.ts │ │ ├── deduplicatePrompt.ts │ │ └── index.ts ├── tsconfig.json └── tsconfig.types.json ├── io ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── LICENSE.md ├── README.md ├── package.json ├── src │ ├── delete │ │ ├── deleteFile.ts │ │ ├── deleteFilesWithName.ts │ │ ├── index.ts │ │ └── removeTemporaryFiles.ts │ ├── fetch │ │ ├── downloadFileAsBase64.ts │ │ └── index.ts │ ├── image │ │ ├── convertImageTo.ts │ │ ├── convertImageToJpeg.ts │ │ ├── convertImageToOriginal.ts │ │ ├── convertImageToPng.ts │ │ ├── convertImageToWebp.ts │ │ ├── imageFormats.ts │ │ ├── index.ts │ │ └── resizeImage.ts │ ├── index.ts │ ├── read │ │ ├── index.ts │ │ ├── readJpegFileToBase64.ts │ │ ├── readLocalOrRemotePlainText.ts │ │ ├── readMp3FileToBase64.ts │ │ ├── readMp4FileToBase64.ts │ │ ├── readPlainText.ts │ │ ├── readPngFileToBase64.ts │ │ └── readWavFileToBase64.ts │ ├── tmp │ │ ├── getRandomDirectory.ts │ │ ├── index.ts │ │ └── uuid.ts │ └── write │ │ ├── index.ts │ │ └── writeBase64ToFile.ts ├── tsconfig.json └── tsconfig.types.json └── timeline ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── LICENSE.md ├── README.md ├── index.html ├── package.json ├── postcss.config.js ├── src ├── ClapTimeline.tsx ├── components │ ├── camera │ │ ├── TimelineCamera.tsx │ │ ├── index.ts │ │ └── types.tsx │ ├── cells │ │ ├── AudioCell.tsx │ │ ├── Cell.tsx │ │ ├── ImageCell.tsx │ │ ├── RedrawButton.tsx │ │ ├── TextCell.tsx │ │ ├── VideoCell.tsx │ │ ├── Waveform.tsx │ │ ├── index.tsx │ │ └── types.ts │ ├── controls │ │ ├── TimelineControls.tsx │ │ ├── index.ts │ │ └── types.ts │ ├── icons │ │ ├── SegmentIcon.tsx │ │ ├── SvgIcon.tsx │ │ ├── SvgShapeMesh.tsx │ │ ├── icons.ts │ │ ├── loadSvgShapes.ts │ │ ├── segmentIcons.ts │ │ ├── types.ts │ │ └── useSvgShapes.ts │ ├── index.ts │ ├── scroller │ │ ├── HorizontalScroller.tsx │ │ ├── VerticalScroller.tsx │ │ └── index.ts │ ├── slider │ │ ├── HorizontalSlider.tsx │ │ ├── TimelineSlider.tsx │ │ ├── TimelineSliderBase.tsx │ │ ├── VerticalSlider.tsx │ │ └── index.ts │ └── timeline │ │ ├── Cells.tsx │ │ ├── Cursor.tsx │ │ ├── Grid.tsx │ │ ├── LeftBarTrackScale.tsx │ │ ├── Timeline.tsx │ │ ├── TopBarTimeScale.tsx │ │ ├── index.ts │ │ └── types.ts ├── compute │ ├── README.md │ ├── computeCellHeight.ts │ └── computeContentSizeMetrics.ts ├── constants │ ├── defaults.ts │ ├── grid.ts │ ├── index.ts │ ├── priorities.ts │ └── themes.ts ├── demo.css ├── demo.tsx ├── hooks │ ├── index.ts │ ├── useAnimationFrame.ts │ ├── useAxis.ts │ ├── useBreakpoints.ts │ ├── useCursorGeometry.ts │ ├── useDebounce.ts │ ├── useHorizontalGridLines.ts │ ├── useHorizontalTrackLines.ts │ ├── useHoveredSegment.ts │ ├── useSegment.ts │ ├── useSegmentChanges.ts │ ├── useSegmentLoader.ts │ ├── useTimeScaleGraduations.ts │ ├── useTimeline.ts │ └── useVerticalGridLines.ts ├── index.tsx ├── types │ ├── grid.ts │ ├── index.ts │ ├── rendering.ts │ ├── theme.ts │ └── timeline.ts └── utils │ ├── clamp.ts │ ├── clapSegmentToTimelineSegment.ts │ ├── cn.ts │ ├── debounceAsync.ts │ ├── debounceSync.ts │ ├── findFreeTrack.ts │ ├── formatTimestamp.ts │ ├── getAudioBuffer.ts │ ├── getDefaultState.ts │ ├── getFinalVideo.ts │ ├── getSegmentColorScheme.ts │ ├── getTextLength.ts │ ├── hslToHex.ts │ ├── index.ts │ ├── parseRenderingStrategy.ts │ ├── readFileAsArrayBuffer.ts │ ├── removeFinalVideosAndConvertToTimelineSegments.ts │ ├── setBodyCursor.ts │ ├── similar.ts │ ├── sleep.ts │ ├── sliceSegments.ts │ ├── throttle.ts │ └── timelineSegmentToClapSegment.ts ├── tailwind.config.js ├── test └── test.tsx ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.types.json └── vite.config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.zip filter=lfs diff=lfs merge=lfs -text 2 | *.wav filter=lfs diff=lfs merge=lfs -text 3 | *.mp3 filter=lfs diff=lfs merge=lfs -text 4 | *.webp filter=lfs diff=lfs merge=lfs -text 5 | *.webm filter=lfs diff=lfs merge=lfs -text 6 | *.mp4 filter=lfs diff=lfs merge=lfs -text 7 | *.gif filter=lfs diff=lfs merge=lfs -text 8 | *.clap filter=lfs diff=lfs merge=lfs -text 9 | *.xcf filter=lfs diff=lfs merge=lfs -text 10 | *.jpeg filter=lfs diff=lfs merge=lfs -text 11 | *.jpg filter=lfs diff=lfs merge=lfs -text 12 | *.csv filter=lfs diff=lfs merge=lfs -text 13 | *.png filter=lfs diff=lfs merge=lfs -text 14 | *.excalidraw filter=lfs diff=lfs merge=lfs -text 15 | *.wasm filter=lfs diff=lfs merge=lfs -text 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | /sandbox/ 39 | /test-results/ 40 | /playwright-report/ 41 | /blob-report/ 42 | /playwright/.cache/ 43 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.17.0 2 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/bun.lockb -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | echo "checking dependencies.." 2 | nvm use 3 | bun i 4 | 5 | echo "building all packages.." 6 | bun run build:all 7 | 8 | echo "uploading assets.." 9 | rm -Rf node_modules/.cache 10 | huggingface-cli upload "jbilcke-hf/clapper" . . --repo-type=space 11 | 12 | -------------------------------------------------------------------------------- /documentation/RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | ## Releasing the whole app 4 | 5 | ### Web version (push to clapper.app) 6 | 7 | TODO 8 | 9 | ### Desktop version (build the Electron app) 10 | 11 | ## Releasing individual modules 12 | 13 | Core package dependencies in the monorepo are defined using "workspace:*" 14 | 15 | But to release them as NPM modules, you need to replace "workspace:*" by the actual version number 16 | 17 | -------------------------------------------------------------------------------- /documentation/diagrams/architecture-draft.excalidraw: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7ff48eec84ac4b2f36ec2ae7f8da08209f2c910241a9540fe9e7464028adfb02 3 | size 81261 4 | -------------------------------------------------------------------------------- /documentation/diagrams/architecture-draft.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b7c7d90f1f76dc55b2854275847afc57d80d8553cb10c2706df467768b81e681 3 | size 2757511 4 | -------------------------------------------------------------------------------- /documentation/screenshots/20240617.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7cbb66f8e8054f04ab079a210157234db3f1833882b81d62ba34682e7647a4bd 3 | size 409433 4 | -------------------------------------------------------------------------------- /documentation/videos/20240826.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:771b8f44a1c7873054f4e9ab53aef9d5fa322ad17326697cacbfe90c8ddfd5d8 3 | size 2312543 4 | -------------------------------------------------------------------------------- /documentation/videos/20240826.mp4: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:394f7fbafff75193a1b3b452a8537d535803c29d0c1d53401d206676872b9518 3 | size 2372984 4 | -------------------------------------------------------------------------------- /packages/app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "prettier" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /packages/app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | /sandbox/ 39 | /test-results/ 40 | /playwright-report/ 41 | /blob-report/ 42 | /playwright/.cache/ 43 | -------------------------------------------------------------------------------- /packages/app/.nvmrc: -------------------------------------------------------------------------------- 1 | v20.17.0 2 | -------------------------------------------------------------------------------- /packages/app/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "semi": false, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "jsxSingleQuote": false, 7 | "plugins": ["prettier-plugin-tailwindcss"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "stone", 10 | "cssVariables": false 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /packages/app/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/app/public/.gitattributes: -------------------------------------------------------------------------------- 1 | *.jpg filter=lfs diff=lfs merge=lfs -text 2 | *.jpeg filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /packages/app/public/carriers/README.md: -------------------------------------------------------------------------------- 1 | # Carriers 2 | 3 | Put carrier videos here (for Live Portrait and other similar tech) -------------------------------------------------------------------------------- /packages/app/public/datasets/baby-names-us-year-of-birth-full.csv: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:897ebf724f4c0fd8d408d13e5babdddeee838a409203701dea89287ec52a612a 3 | size 31419088 4 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/character.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/cloud.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/community.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/computer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/downloads.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/imagefile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/interpolate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/lora.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/misc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/mute.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/project.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/prompt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/screenplay.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/sound.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/soundfile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/speech.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/team.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/text-to-music.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/textfile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/transfer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/transition.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/unpute.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/upscale.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/vendor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/video-folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/videofile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/interface/visible.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/action.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/camera.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/character.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/depth.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/effect.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/packages/app/public/images/icons/segments/effect.svg -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/era.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/event.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/generic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/interface.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/lighting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/mesh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/music.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/phenomenon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/sound.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/storyboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/style.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/time.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/transition.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/icons/segments/weather.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/packages/app/public/images/logos/CL.icns -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.iconset/icon_128x128.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:18f21de4c8d3752d0abe99d9ecb6333773514d0298149934eaa43b11cd5da422 3 | size 4478 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b7378062e0fe84bcf13d157d47983ecd01f885d118ab586bddc1f23c10fb7ce4 3 | size 10574 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.iconset/icon_16x16.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:dbf6e9452dd24b3fb2f486a2c56036bfe9d4ad28e9b5fd94e48dda0a72a0ed46 3 | size 571 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:83d3b304240d3b7733de544c70644baee90d570f31ea60db36e36a8417f28ea2 3 | size 1062 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.iconset/icon_256x256.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b7378062e0fe84bcf13d157d47983ecd01f885d118ab586bddc1f23c10fb7ce4 3 | size 10574 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1b0a4f70ddb6e399c1909b3cb36505fe3534a70d19bb5d9d7740b1cb0965a85e 3 | size 23403 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.iconset/icon_32x32.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:83d3b304240d3b7733de544c70644baee90d570f31ea60db36e36a8417f28ea2 3 | size 1062 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9781017d452c4beeaeae0fab394fa9566789e9818345708fe65068b36f59716e 3 | size 1987 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.iconset/icon_512x512.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1b0a4f70ddb6e399c1909b3cb36505fe3534a70d19bb5d9d7740b1cb0965a85e 3 | size 23403 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:29b51788e206963732d3bf9d5ed19ea5704e7aca705d651d65be38c86cf1ec34 3 | size 67232 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/CL.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1ad55d8b1b92c5d31c1dc66452af1907790dfa5d9d778386f3f12842434e7f6f 3 | size 60404 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/clapper.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b4e966b135bfa1889d8d8656a2325b1b7d7b045e76e03ff06fcc68aa4409031a 3 | size 798190 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/logo-desaturated.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7d23e000d4446c3d2139d779d6ad8bf80bc24c899c00660b006fe47277cee635 3 | size 419043 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/logo-discord-2.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:914019608bd1f1107e9d7d6477f26b98b07c55844dd7f53bb54fe8062c708bfa 3 | size 568110 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/logo-discord-3.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f26ab09e96f7b92408b73a1d212311f674d48f96a7a29b96a6cb27f8884c9212 3 | size 308874 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/logo-no-bg.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4edc9efac487228f65122cbae7dc8b0b2cca8f1f110d6560fe95c8563473b768 3 | size 494692 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/logo-v2.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e167ea9c78ed54a92fe44fc6fcc0247c517516d9298514693abea04230e7ddff 3 | size 328704 4 | -------------------------------------------------------------------------------- /packages/app/public/images/logos/logo-v2.xcf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c05b74fade5a3fc56783d47586c70a32986751b9e60ffed56cd52da7d64a895c 3 | size 2025250 4 | -------------------------------------------------------------------------------- /packages/app/public/images/onboarding/get-started.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:208b7aa03f52a824308fff17c9ec5d854d28ba051bba46ac2b886c7fffe3e688 3 | size 24468 4 | -------------------------------------------------------------------------------- /packages/app/public/images/onboarding/get-started.xcf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:864be2d20925acf9ff343ac45c3d877c656eb2b474f335316526181a204a82be 3 | size 70110 4 | -------------------------------------------------------------------------------- /packages/app/public/images/onboarding/pick-an-example.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:84bda0aa814064c9768a737ca07fe58568129240292b4f1783ef6bb08495b267 3 | size 30473 4 | -------------------------------------------------------------------------------- /packages/app/public/images/onboarding/pick-an-example.xcf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:29d46c5ed0ae1b1a4d9d11cf1103baf52b50ce204221d3667b7f481394ed75ba 3 | size 107646 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/anthropic.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:939a29278bf57d918bbb2d308e48ff82df7b47872c14a05dee61af2ea5a816fd 3 | size 344 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/bigmodel.jpeg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3c2ea43259fb8e8f9b6d736535cb6c7d95481e91bb4dfef1cff54287f74a3f81 3 | size 721 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/civitai.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:abd18aae49334e1e3b8460b564bd3ecdcaaf6dc5600e35b5fcbe5c9cc8fd6371 3 | size 699 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/cohere.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7dcf1ba66dca3fee7cec4c610cbea4bc12e5bdd18b56fe894a83fc12008b047f 3 | size 462 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/comfyicu.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ad12a017d9bdb6aed396e305cb43207247eadeada5f959b3e3b700a98bc9e120 3 | size 4014 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/comfyui.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4ede11b7153e088595273c719e91f29656498ca26d3370505d7ec1e21436fdf9 3 | size 2234 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/elevenlabs.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3a6795e8ed86015e153390923aee6e982a0a7347417dfcb2e04601b43377a920 3 | size 191 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/everartai.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:fd0cdc5b9418eb4336899aa43ccd1003091de4e0d0fc29bbe356050ec4155ea8 3 | size 653 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/falai.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d6a768e01739b4aadd2485a422dfeb0ed226ffeb53be1fd8685b0376741bb590 3 | size 367 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/fireworks.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8614865463f297dff95448d1bddf8f677b56bd7543b5eb2d509a74a05247b294 3 | size 280 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/google.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b48eaf3b82b4dfb8e80deed30cda3afa2017bb1b9d178314a06e84db1bbafc0a 3 | size 1016 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/groq.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:484055c3353fc980811b9062bbc8dce7c739c7f6f7c98b92526dec6435d155cd 3 | size 1201 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/hedra.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d9523dc026a2f41c76ea17ea1a98548bd3ba6dd78c98bf79b37b2e5e978d919d 3 | size 464 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/hotshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:21d55fb1a9eb23edd1b55ae12c33c23929a954b7b1945b945887238a0257150e 3 | size 485 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/huggingface.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e7bb428abe43ebedf135e494cef07d1209534069ad780f2bfa23f2bd0d89b334 3 | size 1789 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/kitsai.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8e33c8a1c4c43c8d03768501de9d5ffee6688a1b122ade606ce9d7111c62eb03 3 | size 229 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/kuaishou.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:74d468290435dd1939dfa054e02f7ff37eaab6e3be56504ce326a6f8d0379224 3 | size 617 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/leonardoai.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ac36015737effd111b7bc669734d8bd07461f5655f6c616c594b5bc7547e36ea 3 | size 1524 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/letzai.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b63b008d252d0b6c1b0cbd4928e049e69f1e1c420fd829d41433501f4ed59874 3 | size 124 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/lumalabs.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8e384d86a4180c9d920c540f40e505c3610407f25bbc7fbf7b82d7b46456c636 3 | size 560 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/midjourney.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2f03098485f0b73156b1fad00917f81a24973e4d794eb7414c181c50e6e1e32e 3 | size 373 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/mistralai.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d140468690efa49983cae48d7c406cc379e3e162a4d9c7630aa7358e037d4ab9 3 | size 524 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/modelslab.jpeg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c3061c58c94530f8bc14accbae864b6091cca99938a7312b454f0cfc258575b2 3 | size 559 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/none.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:322f1dbc17627b1907bab8af68f17914226e186089b1831709a98f2c8f2853ac 3 | size 559 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/openai.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:28bc609287f007651f4b269f157a4149a86fac6367e164555a1c33562d3444b7 3 | size 377 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/piapi.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:69030e64f87e3f85504c3dc4b29687fe81ae368f3906714cc599bf84066bdf89 3 | size 5412 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/replicate.jpeg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0611a798a718459b2ac63c180c662378eba76de3fbff1a1dfa7ce7bafc73970b 3 | size 762 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/runwayml.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:20a681608f9e2481bd2dcfd60f672bd8d79982579893030d0e76ffc28125eba2 3 | size 428 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/stabilityai.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ed8314d355780f8a072d928ff825f2e4e6036996574ac9dc1e7e2030e5f067bf 3 | size 482 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/suno.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b083e13e8db0b703ebd5209991e5368b61d6ce58c3fbb70e39016c7c12fcea61 3 | size 627 4 | -------------------------------------------------------------------------------- /packages/app/public/images/providers/udio.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1896cf5297eecd11d57d52e4126d2ba62d4fcc7b07fe3d71572f57f20a2568f8 3 | size 682 4 | -------------------------------------------------------------------------------- /packages/app/public/samples/claps/Afterglow v10 X Rewrite Bryan E. Harris 2023.clap: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2c86cec4ac6d2494c193b341b33ef08f423f5237743e1a8ecf03949fd5f0e7af 3 | size 157453 4 | -------------------------------------------------------------------------------- /packages/app/public/samples/claps/empty_project.clap: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:42bb7d554f5209f9c0e801c3d71b3b11730f80a2c7a39cf4ac7baf94743d7be1 3 | size 375 4 | -------------------------------------------------------------------------------- /packages/app/public/samples/claps/empty_project.yaml: -------------------------------------------------------------------------------- 1 | - format: clap-0 2 | numberOfEntities: 0 3 | numberOfScenes: 0 4 | numberOfSegments: 27 5 | - id: 5ffcfd0b-cb91-465e-87b6-bde41037373e 6 | title: "" 7 | description: An exciting new project 8 | synopsis: "" 9 | licence: "" 10 | imageRatio: landscape 11 | durationInMs: 18000 12 | width: 1024 13 | height: 575 14 | defaultVideoModel: AnimateDiff-Lightning 15 | imagePrompt: "" 16 | storyPrompt: "" 17 | systemPrompt: "" 18 | isLoop: false 19 | isInteractive: false 20 | bpm: 120 21 | frameRate: 24 22 | - id: e16478cf-3535-48e9-98e8-f702ec9ca348 23 | track: 0 24 | startTimeInMs: 0 25 | endTimeInMs: 0 26 | category: video 27 | outputType: video -------------------------------------------------------------------------------- /packages/app/public/samples/claps/test.clap: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7ce9e63e4c5a5b746fc059fc5cabf3fd0e16d3a697b864500f20fff99f3606fa 3 | size 5425068 4 | -------------------------------------------------------------------------------- /packages/app/public/samples/claps/wasteland.clap: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:fc17a2b987df92cae302c771937895926278fb6739798a5936d310b331b50bab 3 | size 10266887 4 | -------------------------------------------------------------------------------- /packages/app/public/samples/scripts/DISCLAIMER.md: -------------------------------------------------------------------------------- 1 | Those screenplays are aggregated from publicly available source sin the internet. I've merely converted the PDFs them a plain text file. 2 | 3 | THOSE SCRIPTS ARE PROVIDED FOR EDUCATIONAL AND RESEARCH PURPOSES ONLY. 4 | 5 | Please be respectul of the copyright holders and do not use those scripts for commercial purposes. 6 | -------------------------------------------------------------------------------- /packages/app/public/voices/README.md: -------------------------------------------------------------------------------- 1 | Voice files -------------------------------------------------------------------------------- /packages/app/public/wasm/MediaInfoModule.wasm: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a0a209b7ad5152420f6c739f5bf10e9c02df79798f8f75fbe413c6d1be422bbf 3 | size 2355621 4 | -------------------------------------------------------------------------------- /packages/app/src/app/api/assistant/providers/google/README.md: -------------------------------------------------------------------------------- 1 | We can use Gemini 1.5 to analyze a video: 2 | 3 | https://cloud.google.com/vertex-ai/generative-ai/docs/samples/generativeaionvertexai-gemini-video-with-audio 4 | 5 | Important note: not all models do everything! 6 | 7 | https://firebase.google.com/docs/vertex-ai/gemini-models#capabilities-features-comparison 8 | 9 | Gemini 1.5 Pro has more capabilities than the Flash version for instance 10 | -------------------------------------------------------------------------------- /packages/app/src/app/api/assistant/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse, NextRequest } from 'next/server' 2 | 3 | import { AssistantRequest } from '@aitube/clapper-services' 4 | import { askAnyAssistant } from './askAnyAssistant' 5 | 6 | export async function POST(req: NextRequest) { 7 | // do we really need to secure it? 8 | // I mean.. in the end, the user is using their own credentials, 9 | // so they cannot siphon free OpenAI, HF, Replicate tokens 10 | // console.log(`TODO Julian: secure the endpoint`) 11 | // await throwIfInvalidToken(req.headers.get("Authorization")) 12 | 13 | return NextResponse.json( 14 | await askAnyAssistant((await req.json()) as AssistantRequest) 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /packages/app/src/app/api/resolve/providers/aitube/README.md: -------------------------------------------------------------------------------- 1 | # AiTube 2 | 3 | This is an experimental provider that Julian is using to test some ideas. 4 | 5 | Examples include rendering a partial .clap file, 6 | typically a slice (ie a single shot) since it's faster, 7 | but that could be a sequence as well. 8 | -------------------------------------------------------------------------------- /packages/app/src/app/api/resolve/providers/comfy-comfyicu/types.ts: -------------------------------------------------------------------------------- 1 | export type ComfyIcuApiRequestRunWorkflow = { 2 | workflow_id: string 3 | prompt: string 4 | files: Record 5 | } 6 | 7 | export type ComfyIcuApiResponseWorkflowStatus = { 8 | id: string 9 | run_time?: number 10 | status: string 11 | name?: string 12 | created_at: string 13 | output?: ComfyIcuWorkflowOutput[] 14 | project_id: string 15 | api_key_id: any 16 | device?: string 17 | } 18 | 19 | export type ComfyIcuWorkflowOutput = { 20 | filename: string 21 | url: string 22 | thumbnail_url: string 23 | } 24 | -------------------------------------------------------------------------------- /packages/app/src/app/api/resolve/providers/getWorkflowInputValues.ts: -------------------------------------------------------------------------------- 1 | import { ClapInputValues, ClapWorkflow } from '@aitube/clap' 2 | 3 | export function getWorkflowInputValues(workflow: ClapWorkflow): { 4 | workflowDefaultValues: ClapInputValues 5 | workflowValues: ClapInputValues 6 | } { 7 | const workflowDefaultValues = workflow.inputFields.reduce( 8 | (acc, field) => ({ 9 | ...acc, 10 | [field.id]: field.defaultValue, 11 | }), 12 | {} as ClapInputValues 13 | ) 14 | 15 | const workflowValues = workflow.inputValues as ClapInputValues 16 | 17 | return { 18 | workflowDefaultValues, 19 | workflowValues, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/app/src/app/api/resolve/providers/openai/TODO.md: -------------------------------------------------------------------------------- 1 | put functions to generate images using OpenAI 2 | -------------------------------------------------------------------------------- /packages/app/src/app/cute_1024x1024x32.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b4e966b135bfa1889d8d8656a2325b1b7d7b045e76e03ff06fcc68aa4409031a 3 | size 798190 4 | -------------------------------------------------------------------------------- /packages/app/src/app/cute_favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/packages/app/src/app/cute_favicon.ico -------------------------------------------------------------------------------- /packages/app/src/app/cute_icon.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:454715f1d3ccfce112407191cdf78779ba175e98a5af2c82706312ade9008a51 3 | size 583287 4 | -------------------------------------------------------------------------------- /packages/app/src/app/embed/README.md: -------------------------------------------------------------------------------- 1 | # /embed 2 | 3 | `/embed` is a simplified paged used to render a .clap project without 4 | much of the editing UI 5 | 6 | Note that for users without any rendering settings, 7 | this will only be able to playback pre-generated content 8 | -------------------------------------------------------------------------------- /packages/app/src/app/embed/TogglePlayback.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { IoMdPause, IoMdPlay } from 'react-icons/io' 3 | 4 | import { IconSwitch } from '@/components/monitor/icons/icon-switch' 5 | 6 | export function TogglePlayback({ 7 | className = '', 8 | isToggledOn, 9 | onClick, 10 | }: { 11 | className?: string 12 | isToggledOn?: boolean 13 | onClick?: () => void 14 | }) { 15 | return ( 16 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /packages/app/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/packages/app/src/app/favicon.ico -------------------------------------------------------------------------------- /packages/app/src/app/favicon_square.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/packages/app/src/app/favicon_square.ico -------------------------------------------------------------------------------- /packages/app/src/app/fonts/alarm-clock/alarm-clock.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/packages/app/src/app/fonts/alarm-clock/alarm-clock.woff2 -------------------------------------------------------------------------------- /packages/app/src/app/fonts/index.ts: -------------------------------------------------------------------------------- 1 | import localFont from 'next/font/local' 2 | import { Inter } from 'next/font/google' 3 | 4 | export const inter = Inter({ subsets: ['latin'] }) 5 | 6 | // note: this one is an "italic" digital display font 7 | export const clock = localFont({ 8 | src: './alarm-clock/alarm-clock.woff2', 9 | variable: '--font-clock', 10 | }) 11 | 12 | export const fonts = { 13 | clock, 14 | inter, 15 | } 16 | 17 | export const fontList = Object.keys(fonts) 18 | export type FontName = keyof typeof fonts 19 | export const classNames = Object.values(fonts).map((font) => font.className) 20 | export const className = classNames.join(' ') 21 | export type FontClass = 'font-clock' | 'font-inter' 22 | -------------------------------------------------------------------------------- /packages/app/src/app/icon_NEXTJS_BUG.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3d0543040650fa62a8e5aee453cf64cd8f4c6e1eab82b376d395d32dd192577d 3 | size 7087 4 | -------------------------------------------------------------------------------- /packages/app/src/app/icon_square.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1ad55d8b1b92c5d31c1dc66452af1907790dfa5d9d778386f3f12842434e7f6f 3 | size 60404 4 | -------------------------------------------------------------------------------- /packages/app/src/app/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /packages/app/src/components/core/providers/index.ts: -------------------------------------------------------------------------------- 1 | export { ClapWorkflowProviderLogo } from './ClapWorkflowProviderLogo' 2 | export { ClapWorkflowProviderName } from './ClapWorkflowProviderName' 3 | -------------------------------------------------------------------------------- /packages/app/src/components/core/tree/README.md: -------------------------------------------------------------------------------- 1 | This component is adapted from: 2 | 3 | https://www.joshuawootonn.com/react-treeview-component 4 | 5 | See the code here for example usage: 6 | 7 | https://github.com/joshuawootonn/react-components-from-scratch/blob/main/components/treeview/examples/apple-sidebar.tsx 8 | -------------------------------------------------------------------------------- /packages/app/src/components/core/waveform/types.ts: -------------------------------------------------------------------------------- 1 | export enum WaveformRenderingMode { 2 | MONO = 'MONO', 3 | STERO = 'STEREO', 4 | } 5 | -------------------------------------------------------------------------------- /packages/app/src/components/editors/EntityEditor/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex' 2 | import { EntityTree } from './EntityTree' 3 | import { EntityViewer } from './EntityViewer' 4 | 5 | export function EntityEditor() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/src/components/editors/FilterEditor/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex' 2 | import { FilterTree } from './FilterTree' 3 | import { FilterViewer } from './FilterViewer' 4 | 5 | export function FilterEditor() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/src/components/editors/ScriptEditor/README.md: -------------------------------------------------------------------------------- 1 | Monaco editor cheatsheet 2 | 3 | ```typescript 4 | // move the scroll: 5 | editor.setScrollPosition({ scrollTop: horizontalTimelineRatio }) 6 | 7 | // Scroll to a specific line: 8 | editor.revealLine(15) 9 | 10 | // Scroll to a specific line so it ends in the center of the editor: 11 | editor.revealLineInCenter(15) 12 | 13 | // Move current active line: 14 | editor.setPosition({ column: 1, lineNumber: 3 }) 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/app/src/components/editors/ScriptEditor/styles.css: -------------------------------------------------------------------------------- 1 | .monaco-editor-background { 2 | @apply transition-colors; 3 | } 4 | 5 | .entity { 6 | /* 7 | padding and rounded corners only make sense if we use a background: 8 | @apply rounded-xl px-1.5 py-1 font-semibold; 9 | */ 10 | } 11 | 12 | .entity.entity-location { 13 | @apply text-green-300/90 !important; 14 | } 15 | 16 | .entity.entity-character { 17 | @apply text-orange-300/90 !important; 18 | } 19 | 20 | .entity.entity-highlight { 21 | @apply font-semibold; 22 | } 23 | -------------------------------------------------------------------------------- /packages/app/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/clapWorkflowToReactWorkflow.ts: -------------------------------------------------------------------------------- 1 | import { ClapWorkflow, ClapWorkflowEngine } from '@aitube/clap' 2 | 3 | import { ReactWorkflow } from './types' 4 | import { glifToReactWorkflow } from './formats/glif/glifToReactWorkflow' 5 | 6 | export function clapWorkflowToReactWorkflow( 7 | clapWorkflow: ClapWorkflow 8 | ): ReactWorkflow { 9 | if (clapWorkflow.engine === ClapWorkflowEngine.GLIF_WORKFLOW) { 10 | return glifToReactWorkflow(JSON.parse(clapWorkflow.data)) 11 | } 12 | return { nodes: [], edges: [] } 13 | } 14 | -------------------------------------------------------------------------------- /packages/app/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/types.ts: -------------------------------------------------------------------------------- 1 | import { Node, Edge } from '@xyflow/react' 2 | 3 | export type ReactWorkflowNode = Node & { 4 | id: string 5 | type: string 6 | position?: ReactWorkflowNodePosition 7 | // size?: WorkflowNodeSize 8 | data?: ReactWorkflowNodeData 9 | } 10 | 11 | export type ReactWorkflowNodePosition = { 12 | x: number 13 | y: number 14 | } 15 | 16 | /* 17 | export type WorkflowNodeSize = { 18 | width?: number 19 | height?: number 20 | } 21 | */ 22 | 23 | export type ReactWorkflowNodeData = { 24 | label?: string 25 | } & Record 26 | 27 | export type ReactWorkflowEdge = Edge & {} 28 | 29 | export type ReactWorkflow = { 30 | nodes: ReactWorkflowNode[] 31 | edges: ReactWorkflowEdge[] 32 | } 33 | -------------------------------------------------------------------------------- /packages/app/src/components/editors/WorkflowEditor/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex' 2 | import { WorkflowTree } from './WorkflowTree' 3 | import { WorkflowViewer } from './WorkflowViewer' 4 | 5 | export function WorkflowEditor() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/src/components/forms/FormLabel.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | export function FormLabel({ 6 | children, 7 | className, 8 | }: { 9 | children?: ReactNode 10 | className?: string 11 | }) { 12 | return ( 13 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /packages/app/src/components/forms/FormSwitch.tsx: -------------------------------------------------------------------------------- 1 | import { MdOutlineCheckCircle, MdRadioButtonUnchecked } from 'react-icons/md' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | import { FormField } from './FormField' 6 | import { Switch } from '../ui/switch' 7 | 8 | export function FormSwitch({ 9 | label, 10 | className, 11 | checked, 12 | onCheckedChange, 13 | }: { 14 | label?: string 15 | className?: string 16 | checked?: boolean 17 | onCheckedChange: (checked: boolean) => void 18 | }) { 19 | return ( 20 | 21 | { 24 | onCheckedChange(!checked) 25 | }} 26 | /> 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /packages/app/src/components/forms/index.ts: -------------------------------------------------------------------------------- 1 | export { FormArea } from './FormArea' 2 | export { FormDir } from './FormDir' 3 | export { FormField } from './FormField' 4 | export { FormFile } from './FormFile' 5 | export { FormInput } from './FormInput' 6 | export { FormLabel } from './FormLabel' 7 | export { FormRadio } from './FormRadio' 8 | export { FormSection } from './FormSection' 9 | export { FormSelect } from './FormSelect' 10 | export { FormSwitch } from './FormSwitch' 11 | -------------------------------------------------------------------------------- /packages/app/src/components/mobile/MobileMenu.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Sheet, 3 | SheetContent, 4 | SheetDescription, 5 | SheetHeader, 6 | SheetTitle, 7 | SheetTrigger, 8 | } from '@/components/ui/sheet' 9 | 10 | export function MobileMenu() { 11 | return ( 12 | 13 | Open 14 | 15 | 16 | Clapper 17 | 18 | TODO JULIAN: we need a way to display the hierarchical menu on 19 | mobile 20 | 21 | 22 | 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /packages/app/src/components/monitor/README.md: -------------------------------------------------------------------------------- 1 | We are going to need to support multiple rendering modes 2 | 3 | The easiest is the case where we already have a video to show, 4 | let's call it the "full" mode, which we can render using 5 | 6 | Then we have the "dynamic" mode, where we recompose a video from separate assets. 7 | This mode can be rendered using 8 | -------------------------------------------------------------------------------- /packages/app/src/components/monitor/Separator/index.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | 3 | export function Separator({ 4 | className, 5 | color, 6 | }: { 7 | className?: string 8 | color?: string 9 | }) { 10 | return ( 11 |
12 | 13 | : 14 | 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/src/components/monitor/utils/splitElapsedTime.tsx: -------------------------------------------------------------------------------- 1 | export function splitElapsedTime(elapsedTime: number) { 2 | const hours = Math.floor(elapsedTime / (1000 * 60 * 60)).toFixed(0) 3 | elapsedTime %= 1000 * 60 * 60 4 | const minutes = Math.floor(elapsedTime / (1000 * 60)).toFixed(0) 5 | elapsedTime %= 1000 * 60 6 | const seconds = Math.floor(elapsedTime / 1000).toFixed(0) 7 | const milliseconds = (elapsedTime % 1000).toFixed(0) 8 | return { 9 | hours: parseInt(hours), 10 | minutes: parseInt(minutes), 11 | seconds: parseInt(seconds), 12 | milliseconds: parseInt(milliseconds), 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/app/src/components/monitor/utils/zeroPad.ts: -------------------------------------------------------------------------------- 1 | export function zeroPad(num: number, size = 2) { 2 | return String(num).padStart(size, '0') 3 | } 4 | -------------------------------------------------------------------------------- /packages/app/src/components/settings/assistant.tsx: -------------------------------------------------------------------------------- 1 | import { FormSection } from '@/components/forms/FormSection' 2 | 3 | export function SettingsSectionAssistant() { 4 | return ( 5 |
6 | 7 |

8 | No settings for the AI assistant yet. 9 |

10 |

11 | (in the future we might add a system prompt here) 12 |

13 |
14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /packages/app/src/components/tasks/TaskStatusUpdate.tsx: -------------------------------------------------------------------------------- 1 | import { useTasks } from './useTasks' 2 | 3 | export function TaskStatusUpdate({ taskId }: { taskId: string }) { 4 | const { tasks } = useTasks() 5 | 6 | return {tasks[taskId]?.currentMessage || 'Task in progress..'} 7 | } 8 | -------------------------------------------------------------------------------- /packages/app/src/components/toolbars/bottom-bar/index.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | 3 | import { DesktopBottomBar } from './DesktopBottomBar' 4 | import { MobileBottomBar } from './MobileBottomBar' 5 | 6 | export function BottomBar() { 7 | return ( 8 |
15 | 16 | 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /packages/app/src/components/toolbars/bottom-bar/metrics/index.tsx: -------------------------------------------------------------------------------- 1 | import usePerformanceMeter from '@/lib/hooks/usePerformanceMeter' 2 | import { cn } from '@/lib/utils' 3 | 4 | export function Metrics() { 5 | const { isAvailable, isMeasuring, bytes, humanReadableString } = 6 | usePerformanceMeter({ 7 | delayBetweenMeasures: 40, 8 | }) 9 | 10 | if (!isAvailable) { 11 | return null 12 | } 13 | 14 | return ( 15 |
16 | memory usage: 17 | 18 | {!bytes ? 'waiting..' : humanReadableString || ''} 19 | 20 |
21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /packages/app/src/components/toolbars/top-menu/ActivitySpinner/index.tsx: -------------------------------------------------------------------------------- 1 | import { CgSpinnerTwoAlt } from 'react-icons/cg' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | export function ActivitySpinner({ 6 | className = '', 7 | isBusy = false, 8 | color = '', 9 | }: { 10 | className?: string 11 | isBusy?: boolean 12 | color?: string 13 | }) { 14 | return ( 15 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /packages/app/src/components/toolbars/top-menu/IsBusy/index.tsx: -------------------------------------------------------------------------------- 1 | import { GoDotFill } from 'react-icons/go' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | export function IsBusy({ nbPendingTasks = 0 }: { nbPendingTasks?: number }) { 6 | return ( 7 | 0 ? 'animate-pulse opacity-100' : 'opacity-0' 11 | )} 12 | /> 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/app/src/components/toolbars/top-menu/constants.ts: -------------------------------------------------------------------------------- 1 | import { RenderingStrategy } from '@aitube/timeline' 2 | 3 | export const strategyLabels = { 4 | [RenderingStrategy.ON_DEMAND]: 'Render on click', 5 | [RenderingStrategy.ON_SCREEN_ONLY]: 'Render visible items', 6 | [RenderingStrategy.ON_SCREEN_THEN_SURROUNDING]: 'Also render surrounding', 7 | [RenderingStrategy.ON_SCREEN_THEN_ALL]: 'Render all (for GPU-rich people)', 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/src/components/toolbars/top-menu/lists/formatProvider.ts: -------------------------------------------------------------------------------- 1 | import { ClapWorkflowProvider } from '@aitube/clap' 2 | 3 | import { ClapWorkflowProviderShortNames } from '@/components/settings/constants' 4 | 5 | export function formatProvider( 6 | ClapWorkflowProvider?: ClapWorkflowProvider 7 | ): string { 8 | return `${ 9 | (ClapWorkflowProviderShortNames as any)[ClapWorkflowProvider || ''] || 10 | 'None' 11 | }` 12 | } 13 | -------------------------------------------------------------------------------- /packages/app/src/components/tree-browsers/style/treeNodeStyles.ts: -------------------------------------------------------------------------------- 1 | // TODO: this isn't the best place for this as this is style, 2 | // and we are in a state manager 3 | export const libraryClassName = 4 | 'text-sm font-semibold text-white/80 hover:text-white/90' 5 | 6 | export const collectionClassName = 7 | 'text-sm font-normal text-white/70 hover:text-white/90' 8 | 9 | export const itemClassName = 10 | 'text-xs font-light text-white/60 hover:text-white/90' 11 | -------------------------------------------------------------------------------- /packages/app/src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible' 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /packages/app/src/experiments/README.md: -------------------------------------------------------------------------------- 1 | # Experiments 2 | 3 | The is the Clapper Labs, where new ideas are being incubated before integration as real features. 4 | -------------------------------------------------------------------------------- /packages/app/src/lib/hf/adapter/README.md: -------------------------------------------------------------------------------- 1 | Important: if you add a new field, 2 | please make sure you check all the functions inside adapter/\*.ts files 3 | to support it 4 | -------------------------------------------------------------------------------- /packages/app/src/lib/hf/adapter/getDefaultFields.ts: -------------------------------------------------------------------------------- 1 | import { SupportedFields } from '../types' 2 | 3 | export function getDefaultFields(): SupportedFields { 4 | return { 5 | inputPositiveTextPrompt: '', 6 | hasPositiveTextPrompt: false, 7 | inputNegativeTextPrompt: '', 8 | hasNegativeTextPrompt: false, 9 | inputImage: '', 10 | hasInputImage: false, 11 | inputAudio: '', 12 | hasInputAudio: false, 13 | inputWidth: 1024, 14 | hasInputWidth: false, 15 | inputHeight: 574, 16 | hasInputHeight: false, 17 | inputSteps: 8, 18 | hasInputSteps: false, 19 | inputGuidance: 7, 20 | hasInputGuidance: false, 21 | inputSeed: 0, 22 | hasInputSeed: false, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/app/src/lib/hf/cloneSpace.ts: -------------------------------------------------------------------------------- 1 | import { listSpaces, Credentials, whoAmI, SpaceSdk } from '@huggingface/hub' 2 | 3 | export async function cloneSpace({ 4 | id, 5 | apiKey, 6 | }: { 7 | id: string 8 | apiKey: string 9 | }) {} 10 | -------------------------------------------------------------------------------- /packages/app/src/lib/hf/getCurrentOwner.ts: -------------------------------------------------------------------------------- 1 | import { Credentials, whoAmI } from '@huggingface/hub' 2 | 3 | export async function getCurrentOwner(apiKey: string): Promise { 4 | const accessToken = apiKey || '' 5 | 6 | if (!accessToken) { 7 | throw new Error(`cannot list spaces without a Hugging Face access token`) 8 | } 9 | 10 | const credentials: Credentials = { accessToken } 11 | 12 | let username = '' 13 | try { 14 | const { name } = await whoAmI({ credentials }) 15 | username = name 16 | if (!username) { 17 | throw new Error(`returned username is empty`) 18 | } 19 | } catch (err) { 20 | throw new Error(`cannot list spaces: ${err}`) 21 | } 22 | 23 | return username 24 | } 25 | -------------------------------------------------------------------------------- /packages/app/src/lib/hf/getGradioApiInfo.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@gradio/client' 2 | 3 | import { GradioApiInfo } from './types' 4 | import { parseHuggingFaceHubId } from './parseHuggingFaceHubId' 5 | 6 | export async function getGradioApiInfo({ 7 | url, 8 | apiKey, 9 | }: { 10 | url: string 11 | apiKey?: string 12 | }): Promise { 13 | const { ownerAndId } = parseHuggingFaceHubId(url, 'spaces') 14 | 15 | const app = await Client.connect(ownerAndId, { 16 | hf_token: apiKey as any, 17 | }) 18 | const apiInfo: GradioApiInfo = await app.view_api() 19 | return apiInfo 20 | } 21 | -------------------------------------------------------------------------------- /packages/app/src/lib/hooks/README.md: -------------------------------------------------------------------------------- 1 | You will notice that we have useAnimationFrame.ts and useRequestAnimationFrame.ts 2 | 3 | That's accidental, this is following a merge of different codebases 4 | 5 | They work a bit differently from each other, it would be cool if someone could investigate this 6 | -------------------------------------------------------------------------------- /packages/app/src/lib/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useDebounce } from './useDebounce' 2 | export { useDebounceFn } from './useDebounceFn' 3 | export { useOpenFilePicker } from './useOpenFilePicker' 4 | export { useFullscreenStatus } from './useFullscreenStatus' 5 | export { useQueryStringParams } from './useQueryStringParams' 6 | export { useRequestAnimationFrame } from './useRequestAnimationFrame' 7 | -------------------------------------------------------------------------------- /packages/app/src/lib/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | export function useDebounce(value: T, delay?: number): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value) 5 | 6 | useEffect(() => { 7 | const timer = setTimeout(() => setDebouncedValue(value), delay || 500) 8 | 9 | return () => { 10 | clearTimeout(timer) 11 | } 12 | }, [value, delay]) 13 | 14 | return debouncedValue 15 | } 16 | -------------------------------------------------------------------------------- /packages/app/src/lib/hooks/useTitle.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | 3 | export const useTitle = (): { 4 | title: string 5 | changeTitle: (newTitle: string) => void 6 | } => { 7 | const [title, setTitle] = useState(document.title) 8 | 9 | useEffect(() => { 10 | document.title = title 11 | }, [title]) 12 | 13 | const changeTitle = (newTitle: string) => setTitle(newTitle) 14 | 15 | return { title, changeTitle } 16 | } 17 | -------------------------------------------------------------------------------- /packages/app/src/lib/kdenlive/README.md: -------------------------------------------------------------------------------- 1 | This code has been cloned from: 2 | 3 | https://www.npmjs.com/package/kdenlive?activeTab=code 4 | -------------------------------------------------------------------------------- /packages/app/src/lib/kdenlive/entry.ts: -------------------------------------------------------------------------------- 1 | import { Producer } from './producer' 2 | 3 | export class Entry { 4 | constructor( 5 | public producer: Producer, 6 | public in_point: string, 7 | public out_point: string 8 | ) {} 9 | 10 | toXML(): string { 11 | return /* HTML */ `` 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/src/lib/kdenlive/index.ts: -------------------------------------------------------------------------------- 1 | export { Entry } from './entry' 2 | export { Project } from './kdenlive' 3 | export { Playlist, playlistIndexGen } from './playlist' 4 | export { Producer, producerIndexGen } from './producer' 5 | export { Tractor, trackIndexGen } from './tractor' 6 | export { makeIDGen } from './makeIDGen' 7 | -------------------------------------------------------------------------------- /packages/app/src/lib/kdenlive/makeIDGen.ts: -------------------------------------------------------------------------------- 1 | export function* makeIDGen(first = 1): Generator { 2 | let i = first 3 | while (true) { 4 | yield i 5 | i++ 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/base64DataUriToBlob.ts: -------------------------------------------------------------------------------- 1 | export function base64DataUriToBlob(dataURI: string) { 2 | dataURI = dataURI.replace(/^data:/, '') 3 | 4 | const match = dataURI.match(/(?:image|video|audio|text)\/[^;]+/) 5 | const type = match?.[0] || '' 6 | const base64 = Buffer.from(dataURI.replace(/^[^,]+,/, ''), 'base64').toString( 7 | 'binary' 8 | ) 9 | const arrayBuffer = new ArrayBuffer(base64.length) 10 | const typedArray = new Uint8Array(arrayBuffer) 11 | 12 | for (let i = 0; i < base64.length; i++) { 13 | typedArray[i] = base64.charCodeAt(i) 14 | } 15 | 16 | return new Blob([arrayBuffer], { type }) 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/base64DataUriToFile.ts: -------------------------------------------------------------------------------- 1 | export function base64DataUriToFile(dataUrl: string, fileName: string) { 2 | var arr = `${dataUrl || ''}`.split(',') 3 | const st = `${arr[0] || ''}` 4 | const mime = `${st.match(/:(.*?);/)?.[1] || ''}` 5 | const bstr = atob(arr[arr.length - 1]) 6 | let n = bstr.length 7 | const u8arr = new Uint8Array(n) 8 | while (n--) { 9 | u8arr[n] = bstr.charCodeAt(n) 10 | } 11 | return new File([u8arr], fileName, { type: mime }) 12 | } 13 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/base64DataUriToUint8Array.ts: -------------------------------------------------------------------------------- 1 | export function base64DataUriToUint8Array(dataURI: string): Uint8Array { 2 | return Uint8Array.from(atob(dataURI.replace(/^data[^,]+,/, '')), (v) => 3 | v.charCodeAt(0) 4 | ) 5 | } 6 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/blobToBase64DataUri.ts: -------------------------------------------------------------------------------- 1 | export const blobToBase64DataUri = async (blob: Blob): Promise => 2 | new Promise((resolve, reject) => { 3 | const reader = new FileReader() 4 | 5 | reader.onload = (event) => { 6 | const dataUrl = (event.target?.result || '') as string 7 | if (!dataUrl) { 8 | throw new Error(`invalid blob`) 9 | } 10 | resolve(dataUrl) 11 | } 12 | 13 | reader.readAsDataURL(blob) 14 | }) 15 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/cn.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { cn } from './cn' 4 | 5 | test('cn', () => { 6 | expect(cn('p-4 m-4', true ? 'm-4 p-2' : 'p-4 m-2')).toBe('m-4 p-2') 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/convertToJpeg.ts: -------------------------------------------------------------------------------- 1 | import sharp from 'sharp' 2 | 3 | export function convertToJpeg(imageAsBase64DataUri: string): Promise { 4 | const matches = imageAsBase64DataUri.match( 5 | /^data:image\/([A-Za-z-+\/]+);base64,(.+)$/ 6 | ) 7 | if (!matches || matches.length !== 3) { 8 | throw new Error('Invalid input string format') 9 | } 10 | 11 | const imageData = Buffer.from(matches[2], 'base64') 12 | 13 | return sharp(imageData) 14 | .jpeg({ 15 | quality: 97, 16 | }) 17 | .toBuffer() 18 | .then((newImageData) => { 19 | const base64Image = Buffer.from(newImageData).toString('base64') 20 | return `data:image/jpeg;base64,${base64Image}` 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/findClosest.ts: -------------------------------------------------------------------------------- 1 | export function findClosest( 2 | target: number, 3 | numbers: number[] 4 | ): number | undefined { 5 | // Check if the numbers array is empty 6 | if (numbers.length === 0) { 7 | // throw new Error("The numbers array is empty."); 8 | return undefined 9 | } 10 | 11 | // Sort the numbers based on their distance to the target 12 | numbers.sort((a, b) => Math.abs(target - a) - Math.abs(target - b)) 13 | 14 | // Return the number closest to the target 15 | return numbers[0] 16 | } 17 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/formatDuration.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { formatDuration } from './formatDuration' 4 | 5 | test('formatDuration', () => { 6 | expect(formatDuration(0)).toBe('00:00:00:000') 7 | expect(formatDuration(1050)).toBe('00:00:01:050') 8 | expect(formatDuration(60500)).toBe('00:01:00:500') 9 | expect(formatDuration(3600999)).toBe('01:00:00:999') 10 | }) 11 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/formatDuration.ts: -------------------------------------------------------------------------------- 1 | import { intervalToDuration } from 'date-fns' 2 | 3 | export function formatDuration(float_ms: number) { 4 | const duration = intervalToDuration({ start: 0, end: float_ms }) 5 | 6 | const hours = duration.hours || 0 7 | const minutes = duration.minutes || 0 8 | const seconds = duration.seconds || 0 9 | 10 | const total = hours * 60 * 60 * 1000 + minutes * 60 * 1000 + seconds * 1000 11 | 12 | const formatted = [ 13 | duration.hours || 0, 14 | duration.minutes || 0, 15 | duration.seconds || 0, 16 | float_ms - total, 17 | ] 18 | .map((num, i) => String(num as number).padStart(i === 3 ? 3 : 2, '0')) 19 | .join(':') 20 | 21 | return formatted 22 | } 23 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/getValidBoolean.ts: -------------------------------------------------------------------------------- 1 | export const getValidBoolean = (something: any, defaultValue: boolean) => { 2 | if (typeof something === 'boolean') { 3 | return something 4 | } 5 | 6 | const strValue = `${something || defaultValue}`.toLowerCase() 7 | 8 | return strValue === 'true' || strValue === '1' || strValue === 'on' 9 | } 10 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/getValidComfyWorkflowTemplate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Take a string, try to unpack it as JSON to validate it, return a string 3 | * 4 | * @param something 5 | * @param defaultComfyWorkflowTemplate 6 | * @returns 7 | */ 8 | export function getValidComfyWorkflowTemplate( 9 | something: any, 10 | defaultComfyWorkflowTemplate: string 11 | ) { 12 | const strValue = `${something || defaultComfyWorkflowTemplate}` 13 | try { 14 | const workflow = JSON.parse(strValue) 15 | if (typeof workflow === 'object') { 16 | return strValue 17 | } else { 18 | throw new Error( 19 | `this doesn't look like a ComfyUI workflow template string` 20 | ) 21 | } 22 | } catch (err) { 23 | return '{}' 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/getValidNumber.ts: -------------------------------------------------------------------------------- 1 | export const getValidNumber = ( 2 | something: any, 3 | minValue: number, 4 | maxValue: number, 5 | defaultValue: number 6 | ) => { 7 | const strValue = `${something || defaultValue}` 8 | const numValue = Number(strValue) 9 | const isValid = !isNaN(numValue) && isFinite(numValue) 10 | if (!isValid) { 11 | return defaultValue 12 | } 13 | return Math.max(minValue, Math.min(maxValue, numValue)) 14 | } 15 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/getValidString.ts: -------------------------------------------------------------------------------- 1 | export function getValidString(something: any, defaultValue: string) { 2 | const strValue = `${something || defaultValue}` 3 | try { 4 | return JSON.parse(strValue) 5 | } catch (err) { 6 | return strValue 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/isValidNumber.ts: -------------------------------------------------------------------------------- 1 | export function isValidNumber(input?: any) { 2 | return typeof input === 'number' && !isNaN(input) && isFinite(input) 3 | } 4 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/parsePdfToIndentedText.ts: -------------------------------------------------------------------------------- 1 | // TODO Julian: 2 | // use pdfjs to parse a PDF, while preserving the indentation 3 | // 4 | // this is important, because most tools trim spaces which is.. 5 | // not what I want 6 | // 7 | // some references and examples: 8 | // 9 | // https://mozilla.github.io/pdf.js/examples/ 10 | // https://github.com/Utkarsh212/react-pdftotext/blob/main/index.ts 11 | -------------------------------------------------------------------------------- /packages/app/src/lib/utils/sleep.ts: -------------------------------------------------------------------------------- 1 | export const sleep = async (durationInMs: number) => 2 | new Promise((resolve) => { 3 | setTimeout(() => { 4 | resolve(true) 5 | }, durationInMs) 6 | }) 7 | -------------------------------------------------------------------------------- /packages/app/src/services/assistant/askAssistant.ts: -------------------------------------------------------------------------------- 1 | import { AssistantRequest, AssistantMessage } from '@aitube/clapper-services' 2 | 3 | export async function askAssistant(request: AssistantRequest) { 4 | return (await ( 5 | await fetch('/api/assistant', { 6 | method: 'POST', 7 | headers: { 8 | 'Content-Type': 'application/json', 9 | }, 10 | body: JSON.stringify(request), 11 | }) 12 | ).json()) as AssistantMessage 13 | } 14 | -------------------------------------------------------------------------------- /packages/app/src/services/assistant/getDefaultAssistantState.ts: -------------------------------------------------------------------------------- 1 | import { AssistantState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultAssistantState(): AssistantState { 4 | const state: AssistantState = { 5 | history: [], 6 | } 7 | 8 | return state 9 | } 10 | -------------------------------------------------------------------------------- /packages/app/src/services/audio/README.md: -------------------------------------------------------------------------------- 1 | TODO: put those utilities inside an @aitube/audio module 2 | -------------------------------------------------------------------------------- /packages/app/src/services/audio/detectBPM.ts: -------------------------------------------------------------------------------- 1 | // for some reason VS Code is confused when using this 2 | // import { analyze } from "web-audio-beat-detector" 3 | 4 | // but this one works 5 | import { analyze } from 'web-audio-beat-detector/build/es2019/module' 6 | 7 | export async function detectBPM(audioBuffer: AudioBuffer): Promise { 8 | try { 9 | const bpm: number = await analyze(audioBuffer) 10 | 11 | return bpm 12 | } catch (err) { 13 | return 120 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/app/src/services/audio/getDefaultAudioState.ts: -------------------------------------------------------------------------------- 1 | import { AudioState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultAudioState(): AudioState { 4 | const audioContext = 5 | typeof AudioContext !== 'undefined' 6 | ? new AudioContext() 7 | : (undefined as unknown as AudioContext) 8 | 9 | if (!audioContext) { 10 | console.log( 11 | "Note: the audio context isn't available in the current environment" 12 | ) 13 | } 14 | 15 | const state: AudioState = { 16 | isPlaying: false, 17 | isMuted: false, 18 | userDefinedGain: 1.0, 19 | currentGain: 1.0, 20 | audioContext, 21 | currentlyPlaying: [], 22 | } 23 | 24 | return state 25 | } 26 | -------------------------------------------------------------------------------- /packages/app/src/services/autocomplete/getDefaultAutocompleteState.ts: -------------------------------------------------------------------------------- 1 | import { AutocompleteState } from './types' 2 | 3 | export function getDefaultAutocompleteState(): AutocompleteState { 4 | const state: AutocompleteState = { 5 | isRunning: false, 6 | } 7 | return state 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/src/services/autocomplete/types.ts: -------------------------------------------------------------------------------- 1 | export type AutocompleteState = { 2 | isRunning: boolean 3 | } 4 | export type AutocompleteControls = { 5 | /** 6 | * Take a range of storyboards and infer the corresponding story 7 | * 8 | * This will directly update the screenplay and timeline, 9 | * creating the appropriate segments, line coordinates etc 10 | * 11 | * 12 | * @param params 13 | * @returns 14 | */ 15 | convertImagesToStory: (params?: { 16 | startTimeInMs?: number 17 | endTimeInMs?: number 18 | }) => Promise 19 | } 20 | export type AutocompleteStore = AutocompleteState & AutocompleteControls 21 | -------------------------------------------------------------------------------- /packages/app/src/services/broadcast/constants.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/packages/app/src/services/broadcast/constants.ts -------------------------------------------------------------------------------- /packages/app/src/services/broadcast/getDefaultBroadcastState.ts: -------------------------------------------------------------------------------- 1 | import { BroadcastState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultBroadcastState(): BroadcastState { 4 | const state: BroadcastState = { 5 | isBroadcasting: false, 6 | } 7 | return state 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/entity-editor/getDefaultEntityEditorState.ts: -------------------------------------------------------------------------------- 1 | import { EntityEditorState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultEntityEditorState(): EntityEditorState { 4 | const state: EntityEditorState = { 5 | before: [], 6 | current: undefined, 7 | after: [], 8 | version: 0, 9 | 10 | draft: undefined, 11 | showEntityList: false, 12 | } 13 | 14 | return state 15 | } 16 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/filter-editor/README.md: -------------------------------------------------------------------------------- 1 | Utilities to apply color grading to an image. 2 | 3 | The purpose is to be able to homogenize the colors of storyboards, 4 | as well as providing useful filters. 5 | 6 | I'm going to put this color grading module into its own library, as I would like to use it in other applications and demos. 7 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/filter-editor/getDefaultFilterEditorState.ts: -------------------------------------------------------------------------------- 1 | import { FilterEditorState } from '@aitube/clapper-services' 2 | 3 | import { cinematic, filters } from './filters' 4 | 5 | export function getDefaultFilterEditorState(): FilterEditorState { 6 | const state: FilterEditorState = { 7 | before: [], 8 | current: undefined, 9 | after: [], 10 | version: 0, 11 | 12 | isEnabled: true, 13 | 14 | availableFilters: [...filters], 15 | 16 | activeFilters: [ 17 | { 18 | filter: cinematic, 19 | parameters: { 20 | preset: 'Blade Runner', 21 | intensity: 0.6, 22 | contrast: 1.3, 23 | }, 24 | }, 25 | ], 26 | } 27 | 28 | return state 29 | } 30 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/getDefaultEditorsState.ts: -------------------------------------------------------------------------------- 1 | import { EditorsState, EditorView } from '@aitube/clapper-services' 2 | 3 | export function getDefaultEditorsState(): EditorsState { 4 | const state: EditorsState = { 5 | view: EditorView.SCRIPT, 6 | } 7 | 8 | return state 9 | } 10 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/index.ts: -------------------------------------------------------------------------------- 1 | export { useEntityEditor } from './entity-editor/useEntityEditor' 2 | export { useFilterEditor } from './filter-editor/useFilterEditor' 3 | export { useProjectEditor } from './project-editor/useProjectEditor' 4 | export { useSegmentEditor } from './segment-editor/useSegmentEditor' 5 | export { useScriptEditor } from './script-editor/useScriptEditor' 6 | export { useWorkflowEditor } from './workflow-editor/useWorkflowEditor' 7 | export { useEditors } from './useEditors' 8 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/project-editor/getDefaultProjectEditorState.ts: -------------------------------------------------------------------------------- 1 | import { ProjectEditorState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultProjectEditorState(): ProjectEditorState { 4 | const state: ProjectEditorState = { 5 | before: [], 6 | current: undefined, 7 | after: [], 8 | version: 0, 9 | } 10 | 11 | return state 12 | } 13 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/script-editor/getDefaultScriptEditorState.ts: -------------------------------------------------------------------------------- 1 | import { ScriptEditorState, EditorView } from '@aitube/clapper-services' 2 | 3 | export function getDefaultScriptEditorState(): ScriptEditorState { 4 | const state: ScriptEditorState = { 5 | monaco: undefined, 6 | textModel: undefined, 7 | standaloneCodeEditor: undefined, 8 | mouseIsInside: false, 9 | lineNumberToMentionedSegments: {}, 10 | 11 | lastPublished: '', 12 | 13 | scrollHeight: 0, 14 | scrollLeft: 0, 15 | scrollTop: 0, 16 | scrollWidth: 0, 17 | scrollTopInMs: 0, 18 | 19 | before: [], 20 | current: undefined, 21 | after: [], 22 | version: 0, 23 | } 24 | 25 | return state 26 | } 27 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/segment-editor/getDefaultSegmentEditorState.ts: -------------------------------------------------------------------------------- 1 | import { SegmentEditorState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultSegmentEditorState(): SegmentEditorState { 4 | const state: SegmentEditorState = { 5 | before: [], 6 | current: undefined, 7 | after: [], 8 | version: 0, 9 | } 10 | 11 | return state 12 | } 13 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/segment-editor/useSegmentEditor.ts: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { create } from 'zustand' 4 | import { SegmentEditorStore } from '@aitube/clapper-services' 5 | 6 | import { getDefaultSegmentEditorState } from './getDefaultSegmentEditorState' 7 | import { TimelineSegment } from '@aitube/timeline' 8 | 9 | export const useSegmentEditor = create((set, get) => ({ 10 | ...getDefaultSegmentEditorState(), 11 | setCurrent: (current?: TimelineSegment) => { 12 | set({ current }) 13 | }, 14 | undo: () => {}, 15 | redo: () => {}, 16 | })) 17 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/useEditors.ts: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { create } from 'zustand' 4 | import { EditorsStore, EditorView } from '@aitube/clapper-services' 5 | 6 | import { getDefaultEditorsState } from './getDefaultEditorsState' 7 | 8 | export const useEditors = create((set, get) => ({ 9 | ...getDefaultEditorsState(), 10 | setView: (view: EditorView) => { 11 | set({ view }) 12 | }, 13 | })) 14 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/getDefaultWorkflowEditorState.ts: -------------------------------------------------------------------------------- 1 | import { WorkflowEditorState } from '@aitube/clapper-services' 2 | 3 | import { staticWorkflows } from './workflows' 4 | 5 | export function getDefaultWorkflowEditorState(): WorkflowEditorState { 6 | const state: WorkflowEditorState = { 7 | before: [], 8 | current: undefined, 9 | after: [], 10 | version: 0, 11 | 12 | // we clone 13 | availableWorkflows: staticWorkflows.map((w) => ({ ...w })), 14 | } 15 | 16 | return state 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/workflows/civitai/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClapWorkflow, 3 | ClapWorkflowEngine, 4 | ClapWorkflowCategory, 5 | ClapWorkflowProvider, 6 | } from '@aitube/clap' 7 | import { 8 | genericHeight1024, 9 | genericPrompt, 10 | genericWidth1024, 11 | } from '../common/defaultValues' 12 | 13 | // ------------------------------------------------------------------------------ 14 | // if a user is already using one of those workflows and you change its settings, 15 | // they will have to reselect it in the UI for changes to be taken into account. 16 | // 17 | // -> we can create a ticket to fix this 18 | // ------------------------------------------------------------------------------ 19 | export const civitaiWorkflows: ClapWorkflow[] = [] 20 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/workflows/comfydeploy/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClapWorkflow, 3 | ClapWorkflowEngine, 4 | ClapWorkflowCategory, 5 | ClapWorkflowProvider, 6 | } from '@aitube/clap' 7 | 8 | // ------------------------------------------------------------------------------ 9 | // if a user is already using one of those workflows and you change its settings, 10 | // they will have to reselect it in the UI for changes to be taken into account. 11 | // 12 | // -> we can create a ticket to fix this 13 | // ------------------------------------------------------------------------------ 14 | export const comfydeployWorkflows: ClapWorkflow[] = [] 15 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/workflows/comfyui/getComfyWorkflow.ts: -------------------------------------------------------------------------------- 1 | import { ClapSegmentCategory } from '@aitube/clap' 2 | import { useSettings } from '@/services' 3 | 4 | export function getComfyWorkflow(category: ClapSegmentCategory) { 5 | const settings = useSettings.getState() 6 | 7 | let comfyWorkflow 8 | 9 | if (category === ClapSegmentCategory.IMAGE) { 10 | comfyWorkflow = settings.comfyClapWorkflowForImage 11 | } else if (category === ClapSegmentCategory.VIDEO) { 12 | comfyWorkflow = settings.comfyClapWorkflowForVideo 13 | } 14 | 15 | return JSON.stringify(comfyWorkflow || {}) 16 | } 17 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/workflows/common/loras/canWorkflowUseLora.ts: -------------------------------------------------------------------------------- 1 | import { ClapInputCategory, ClapWorkflow } from '@aitube/clap/dist/types' 2 | 3 | export function canWorkflowUseLora(workflow: ClapWorkflow): boolean { 4 | return workflow.inputFields.some( 5 | ({ category }) => category === ClapInputCategory.LORA 6 | // category === ClapInputCategory.LORA_HF_MODEL || 7 | // category === ClapInputCategory.LORA_WEIGHT_URL 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/workflows/common/loras/getWorkflowInputField.ts: -------------------------------------------------------------------------------- 1 | import { ClapInputCategory, ClapInputField, ClapWorkflow } from '@aitube/clap' 2 | 3 | export function getWorkflowInputField( 4 | workflow: ClapWorkflow, 5 | category: ClapInputCategory 6 | ): ClapInputField | undefined { 7 | return workflow.inputFields.find((field) => field.category === category) 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/workflows/falai/comfyuiWorkflows.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClapWorkflow, 3 | ClapWorkflowEngine, 4 | ClapWorkflowCategory, 5 | ClapWorkflowProvider, 6 | } from '@aitube/clap' 7 | 8 | export const comfyuiWorkflows: ClapWorkflow[] = [] 9 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/workflows/falai/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultWorkflows } from './defaultWorkflows' 2 | import { comfyuiWorkflows } from './comfyuiWorkflows' 3 | 4 | export const falaiWorkflows = [...defaultWorkflows, ...comfyuiWorkflows] 5 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/workflows/hotshot/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClapWorkflow, 3 | ClapWorkflowEngine, 4 | ClapWorkflowCategory, 5 | ClapWorkflowProvider, 6 | } from '@aitube/clap' 7 | import { 8 | genericHeight1024, 9 | genericPrompt, 10 | genericWidth1024, 11 | } from '../common/defaultValues' 12 | 13 | // ------------------------------------------------------------------------------ 14 | // if a user is already using one of those workflows and you change its settings, 15 | // they will have to reselect it in the UI for changes to be taken into account. 16 | // 17 | // -> we can create a ticket to fix this 18 | // ------------------------------------------------------------------------------ 19 | export const hotshotWorkflows: ClapWorkflow[] = [] 20 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/workflows/replicate/comfyuiWorkflows.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClapWorkflow, 3 | ClapWorkflowEngine, 4 | ClapWorkflowCategory, 5 | ClapWorkflowProvider, 6 | } from '@aitube/clap' 7 | 8 | export const comfyuiWorkflows: ClapWorkflow[] = [] 9 | -------------------------------------------------------------------------------- /packages/app/src/services/editors/workflow-editor/workflows/replicate/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultWorkflows } from './defaultWorkflows' 2 | import { comfyuiWorkflows } from './comfyuiWorkflows' 3 | 4 | export const replicateWorkflows = [...defaultWorkflows, ...comfyuiWorkflows] 5 | -------------------------------------------------------------------------------- /packages/app/src/services/inputs/getDefaultInputsState.ts: -------------------------------------------------------------------------------- 1 | import { InputsState } from './types' 2 | 3 | export function getDefaultInputsState(): InputsState { 4 | const state: InputsState = { 5 | hasGamepads: false, 6 | gamepads: {}, 7 | animationFrame: 0, 8 | } 9 | 10 | return state 11 | } 12 | -------------------------------------------------------------------------------- /packages/app/src/services/inputs/types.ts: -------------------------------------------------------------------------------- 1 | export type InputsState = { 2 | hasGamepads: boolean 3 | gamepads: Record 4 | animationFrame: number 5 | } 6 | export type InputsControls = { 7 | init: () => void 8 | scanGamepads: () => void 9 | } 10 | export type InputsStore = InputsState & InputsControls 11 | -------------------------------------------------------------------------------- /packages/app/src/services/io/fileDataToBase64.ts: -------------------------------------------------------------------------------- 1 | import { FileData } from '@ffmpeg/ffmpeg/dist/esm/types' 2 | 3 | export function fileDataToBase64(fileData: FileData): string { 4 | // Convert Uint8Array to Base64 string without using btoa 5 | let binary = '' 6 | const bytes = new Uint8Array(fileData as any) 7 | const len = bytes.byteLength 8 | for (let i = 0; i < len; i++) { 9 | binary += String.fromCharCode(bytes[i]) 10 | } 11 | 12 | return window.btoa(binary) 13 | } 14 | -------------------------------------------------------------------------------- /packages/app/src/services/io/formats/index.ts: -------------------------------------------------------------------------------- 1 | export { generateEDL } from './edl' 2 | export { generateFCP } from './fcp' 3 | export { generateMLT } from './mlt' 4 | export { generateOTIO } from './otio' 5 | export { generateOTIOZ } from './otioz' 6 | -------------------------------------------------------------------------------- /packages/app/src/services/io/getDefaultIOState.ts: -------------------------------------------------------------------------------- 1 | import { IOState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultIOState(): IOState { 4 | return {} 5 | } 6 | -------------------------------------------------------------------------------- /packages/app/src/services/io/parseFileName.ts: -------------------------------------------------------------------------------- 1 | export function parseFileName(input: any): { 2 | projectName: string 3 | fileName: string 4 | extension: string 5 | } { 6 | const fileName = `${input || ''}` 7 | const bits = fileName.split('.') 8 | const extension = `${bits.pop() || ''}`.toLowerCase() 9 | const projectName = bits.join(' ') 10 | 11 | return { 12 | projectName, 13 | fileName, 14 | extension, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/app/src/services/io/parseFilesIntoSegments.ts: -------------------------------------------------------------------------------- 1 | import { ClapSegment } from '@aitube/clap' 2 | import { parseFileIntoSegments } from './parseFileIntoSegments' 3 | 4 | export async function parseFilesIntoSegments({ 5 | files, 6 | }: { 7 | /** 8 | * The files to import 9 | */ 10 | files: File[] 11 | }): Promise { 12 | return ( 13 | await Promise.all( 14 | files.map((file) => 15 | parseFileIntoSegments({ 16 | file, 17 | }) 18 | ) 19 | ) 20 | ).reduce((acc, segments) => [...acc, ...segments], []) 21 | } 22 | -------------------------------------------------------------------------------- /packages/app/src/services/metrics/getDefaultClapWorkflowProviderMetrics.ts: -------------------------------------------------------------------------------- 1 | import { ClapWorkflowProviderMetrics } from './types' 2 | 3 | export function getDefaultClapWorkflowProviderMetrics(): ClapWorkflowProviderMetrics { 4 | const metrics: ClapWorkflowProviderMetrics = { 5 | // used to estimate live wait times 6 | averageDurationPerModel: {}, 7 | 8 | // used to count the total compute time per model 9 | totalDurationPerModel: {}, 10 | 11 | // used to estimate the total cost per model 12 | totalCostPerModel: {}, 13 | 14 | // the total cost for this provider 15 | totalCost: 0, 16 | } 17 | return metrics 18 | } 19 | -------------------------------------------------------------------------------- /packages/app/src/services/metrics/getDefaultMetricsModelEstimations.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ProviderMetricsEstimationType, 3 | ProviderMetricsModelEstimations, 4 | } from './types' 5 | 6 | export function getDefaultMetricsModelEstimation(): ProviderMetricsModelEstimations { 7 | const defaults: ProviderMetricsModelEstimations = { 8 | estimationType: ProviderMetricsEstimationType.UNAVAILABLE, 9 | averageCostPerComputeTimeInSec: 0, // 0.01, 10 | averageDurationInSec: 0, // 7, 11 | averageCostPerGeneration: 0, // 0.07 12 | } 13 | return defaults 14 | } 15 | -------------------------------------------------------------------------------- /packages/app/src/services/metrics/getDefaultMetricsModelStats.ts: -------------------------------------------------------------------------------- 1 | import { ProviderMetricsModelStats } from './types' 2 | 3 | export function getDefaultMetricsModelStats(): ProviderMetricsModelStats { 4 | const stats: ProviderMetricsModelStats = { 5 | averageCostPerComputeTimeInSec: 0, 6 | averageDurationInSec: 0, 7 | averageCostPerGeneration: 0, 8 | computeTimeInSec: 0, 9 | totalCost: 0, 10 | } 11 | return stats 12 | } 13 | -------------------------------------------------------------------------------- /packages/app/src/services/metrics/getDefaultMetricsState.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultMetricsPerProvider } from './getDefaultMetricsPerProvider' 2 | import { MetricsState } from './types' 3 | 4 | export function getDefaultMetricsState(): MetricsState { 5 | const defaults: MetricsState = { 6 | metricsPerProvider: getDefaultMetricsPerProvider(), 7 | totalCostSinceAppStarted: 0, 8 | } 9 | return defaults 10 | } 11 | -------------------------------------------------------------------------------- /packages/app/src/services/metrics/useMetrics.ts: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { create } from 'zustand' 4 | 5 | import { MetricsStore } from './types' 6 | import { getDefaultMetricsState } from './getDefaultMetricsState' 7 | 8 | export const useMetrics = create((set, get) => ({ 9 | ...getDefaultMetricsState(), 10 | 11 | // TODO: add a track metric callback 12 | })) 13 | -------------------------------------------------------------------------------- /packages/app/src/services/mic/getDefaultMicState.ts: -------------------------------------------------------------------------------- 1 | import { MicState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultMicState(): MicState { 4 | return { 5 | isSupported: 6 | typeof window === 'undefined' || !('webkitSpeechRecognition' in window), 7 | isListening: false, 8 | transcript: '', 9 | interimResults: true, 10 | error: '', 11 | lang: 'en-US', 12 | grammar: 13 | '#JSGF V1.0; grammer punctuation; public =. |, |? | | ; | : ;', 14 | grammarWeight: 1, 15 | continuous: false, 16 | recognition: undefined, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/app/src/services/monitor/README.md: -------------------------------------------------------------------------------- 1 | TODO: put those into an aitube/monitor package 2 | -------------------------------------------------------------------------------- /packages/app/src/services/monitor/getDefaultMonitorState.ts: -------------------------------------------------------------------------------- 1 | import { MonitorState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultMonitorState(): MonitorState { 4 | const state: MonitorState = { 5 | shortcutsAreBound: false, 6 | lastTimelineUpdateAtInMs: 0, 7 | isPlaying: false, 8 | staticVideoRef: undefined, 9 | isEmbedded: false, 10 | } 11 | 12 | return state 13 | } 14 | -------------------------------------------------------------------------------- /packages/app/src/services/plugins/constants.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/packages/app/src/services/plugins/constants.ts -------------------------------------------------------------------------------- /packages/app/src/services/plugins/getDefaultPluginsState.ts: -------------------------------------------------------------------------------- 1 | import { PluginsState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultPluginsState(): PluginsState { 4 | const state: PluginsState = { 5 | availablePlugins: [], 6 | installedPlugins: {}, 7 | } 8 | 9 | return state 10 | } 11 | -------------------------------------------------------------------------------- /packages/app/src/services/renderer/constants.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DEFAULT_COLUMNS_PER_SLICE, 3 | DEFAULT_DURATION_IN_MS_PER_STEP, 4 | } from '@aitube/timeline' 5 | 6 | // how much we look up in the future 7 | export const blockSizeInMs = 8 | DEFAULT_COLUMNS_PER_SLICE * // this equals to 4 at the time of writing, but this might become dynamic 9 | DEFAULT_DURATION_IN_MS_PER_STEP // this equals to 500 at the time of writing 10 | 11 | // max refresh rate (eg. 100 ms) 12 | // if the user has a fast device you can try faster rates 13 | export const ACTIVE_SEGMENTS_REFRESH_RATE_IN_MS = 100 14 | -------------------------------------------------------------------------------- /packages/app/src/services/renderer/getDefaultBufferedSegments.ts: -------------------------------------------------------------------------------- 1 | import { BufferedSegments } from '@aitube/clapper-services' 2 | 3 | export function getDefaultBufferedSegments(): BufferedSegments { 4 | const result: BufferedSegments = { 5 | activeSegmentsCacheKey: '', 6 | activeSegments: [], 7 | activeVideoSegment: undefined, 8 | activeStoryboardSegment: undefined, 9 | activeAudioSegments: [], 10 | 11 | upcomingSegmentsCacheKey: '', 12 | upcomingSegments: [], 13 | upcomingVideoSegment: undefined, 14 | upcomingStoryboardSegment: undefined, 15 | } 16 | return result 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/src/services/renderer/getSegmentCacheKey.ts: -------------------------------------------------------------------------------- 1 | import { TimelineSegment } from '@aitube/timeline' 2 | 3 | export function getSegmentCacheKey(segment: TimelineSegment, prefix = '') { 4 | // we have to be smart here because we can't take the full base64 assetUrl (it might be huge) 5 | // so we only use a portion of it 6 | 7 | return `${prefix}:${segment.id}_${segment.assetUrl.slice(0, 1024)}` 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/src/services/renderer/index.ts: -------------------------------------------------------------------------------- 1 | export { blockSizeInMs, ACTIVE_SEGMENTS_REFRESH_RATE_IN_MS } from './constants' 2 | export { getDefaultBufferedSegments } from './getDefaultBufferedSegments' 3 | export { getDefaultRendererState } from './getDefaultRendererState' 4 | export { getSegmentCacheKey } from './getSegmentCacheKey' 5 | export { useRenderer } from './useRenderer' 6 | export { useRenderLoop } from './useRenderLoop' 7 | -------------------------------------------------------------------------------- /packages/app/src/services/resolver/constants.ts: -------------------------------------------------------------------------------- 1 | // TODO: we should take into account the rate limit of our provider 2 | // but I think this can be the responsibility of the parent app 3 | export const DEFAULT_WAIT_TIME_IF_NOTHING_TO_DO_IN_MS = 500 4 | -------------------------------------------------------------------------------- /packages/app/src/services/settings/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getDefaultSettingsState' 2 | export * from './useSettings' 3 | -------------------------------------------------------------------------------- /packages/app/src/services/simulator/useDefaultSimulatorState.ts: -------------------------------------------------------------------------------- 1 | import { SimulatorState } from '@aitube/clapper-services' 2 | 3 | export function getDefaultSimulatorState(): SimulatorState { 4 | const state: SimulatorState = { 5 | isRunning: false, 6 | } 7 | return state 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/src/services/simulator/useSimulator.ts: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { create } from 'zustand' 4 | import { SimulatorStore } from '@aitube/clapper-services' 5 | 6 | import { getDefaultSimulatorState } from './useDefaultSimulatorState' 7 | 8 | export const useSimulator = create((set, get) => ({ 9 | ...getDefaultSimulatorState(), 10 | 11 | startLoop: () => {}, 12 | runLoop: async () => {}, 13 | togglePause: (isPaused?: boolean) => { 14 | let isRunning = get().isRunning 15 | if (typeof isPaused === 'boolean') { 16 | isRunning = !isPaused 17 | } else { 18 | isRunning = !isRunning 19 | } 20 | 21 | set({ isRunning }) 22 | return isRunning 23 | }, 24 | })) 25 | -------------------------------------------------------------------------------- /packages/app/src/services/ui/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getDefaultUIState' 2 | export * from './useUI' 3 | -------------------------------------------------------------------------------- /packages/app/src/services/ui/useTheme.ts: -------------------------------------------------------------------------------- 1 | import { UITheme } from '@aitube/clapper-services' 2 | 3 | import { useUI } from './useUI' 4 | 5 | export function useTheme(): UITheme { 6 | const getTheme = useUI((s) => s.getTheme) 7 | const themeName = useUI((s) => s.themeName) 8 | const theme = getTheme() 9 | return theme 10 | } 11 | -------------------------------------------------------------------------------- /packages/app/tests/main.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('has title', async ({ page }) => { 4 | await page.goto('http://localhost:3000/'); 5 | 6 | // Expect a title "to contain" a substring. 7 | await expect(page).toHaveTitle(/Clapper/); 8 | }); 9 | 10 | test('get started link', async ({ page }) => { 11 | await page.goto('http://localhost:3000/'); 12 | 13 | // TODO: replace by our own real tests 14 | // Click the get started link. 15 | // await page.getByRole('link', { name: 'Get started' }).click(); 16 | 17 | // Expects page to have a heading with the name of Installation. 18 | // await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/app/vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | test: { 7 | environment: 'jsdom', 8 | exclude: [ 9 | '**/node_modules/**', 10 | '**/.next/**', 11 | '**/tests/**', // <- we ignore since those are Playwright tests, not Vitest tests 12 | ], 13 | } 14 | }) -------------------------------------------------------------------------------- /packages/broadway/.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # Except the dist directory 4 | !dist/ 5 | -------------------------------------------------------------------------------- /packages/broadway/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/broadway/samples/claps/Afterglow v10 X Rewrite Bryan E. Harris 2023.clap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/packages/broadway/samples/claps/Afterglow v10 X Rewrite Bryan E. Harris 2023.clap -------------------------------------------------------------------------------- /packages/broadway/src/analysis/isDialogueLine.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | // /!\ This whole file was generated with GPT-4 4 | 5 | 6 | /** 7 | * Check whether the given line contains a character dialogue or action bitmap. 8 | * @param fullLine the line to check. Might contain spaces. 9 | */ 10 | export function isDialogueLine(fullLine: string): boolean { 11 | 12 | const containsTabulation = fullLine.startsWith(" ") 13 | 14 | // this rule is a bit strict as some text files don't have tabulation.. 15 | // but it helps A LOT 16 | if (!containsTabulation) { 17 | return false 18 | } 19 | 20 | return true 21 | } 22 | -------------------------------------------------------------------------------- /packages/broadway/src/analysis/isPageSeparator.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | // might be deprecated actually 4 | /** 5 | * Checks whether a line in the script is PDF page separators. 6 | * @param line The line to check. 7 | */ 8 | export function isPageSeparator(line: string): boolean { 9 | const pageSeparatorPattern = /^\s*-\d+-\s*$/i; 10 | return pageSeparatorPattern.test(line); 11 | } -------------------------------------------------------------------------------- /packages/broadway/src/analysis/isVoiceOver.ts: -------------------------------------------------------------------------------- 1 | export function isVoiceOver(input: string) { 2 | const text = input.trim().toLowerCase() 3 | if (text === "v.o." || 4 | text === "v.o" || 5 | text === "vo." || 6 | text === "(v.o.)" || 7 | text === "(v.o)" || 8 | text === "(vo.)" || 9 | text === "voice over" || 10 | text === "(voice over)" 11 | ) { 12 | return true 13 | } 14 | return false 15 | } -------------------------------------------------------------------------------- /packages/broadway/src/analysis/normalizeName.ts: -------------------------------------------------------------------------------- 1 | export function normalizeName(name: string) { 2 | const normalizedName = `${name || ""}`.trim().toLowerCase() 3 | 4 | return normalizedName 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/broadway/src/analysis/parseCharacterName.ts: -------------------------------------------------------------------------------- 1 | function takeWhatsBefore(input: string, pattern: string): string { 2 | return `${input || ""}`.split(pattern).shift() || "" 3 | } 4 | 5 | export function parseCharacterName(input: string) { 6 | 7 | let tmp = takeWhatsBefore(input, "'S") 8 | tmp = takeWhatsBefore(tmp, "’S") 9 | tmp = takeWhatsBefore(tmp, "(") 10 | tmp = takeWhatsBefore(tmp, "[") 11 | 12 | tmp = tmp 13 | .replaceAll(" INTERNALS ", "") 14 | .replaceAll(" VOICE ", "") 15 | 16 | tmp = tmp.trim() 17 | 18 | /* 19 | // console.log("DEBUG:", { 20 | input: ` ${input || ""} `, 21 | tmp 22 | }) 23 | */ 24 | 25 | return tmp 26 | } -------------------------------------------------------------------------------- /packages/broadway/src/analysis/parseDialogueLine.ts: -------------------------------------------------------------------------------- 1 | import { isAllCaps } from "@/utils/isAllCaps" 2 | 3 | 4 | export function parseDialogueLine(line: string): string { 5 | let lineWithSpaces = ` ${line || ""} ` 6 | 7 | if (line === " .. " || line === " ... ") { 8 | return "" 9 | } 10 | 11 | // looks like an anomaly, normally dialogue isn't all caps 12 | if (isAllCaps(lineWithSpaces)) { 13 | return "" 14 | } 15 | 16 | // some scripts (such as Afterglow) have quotes in it 17 | // we can remove them 18 | lineWithSpaces = lineWithSpaces.replaceAll(`“`, '').replaceAll(`”`, '') 19 | 20 | return lineWithSpaces.trim() 21 | } -------------------------------------------------------------------------------- /packages/broadway/src/constants/general.ts: -------------------------------------------------------------------------------- 1 | // the time resolution - this may be an option in the future 2 | export const DEFAULT_DURATION_IN_MS_PER_STEP = 500 3 | 4 | export const DEFAULT_COLUMNS_PER_STEP = 1 // this is dynamic, and corresponds to the zoom level 5 | 6 | // how many columns per slice / default segment lenght - this may be an option in the future 7 | // so here, a "slice" (a typical shot) 8 | // lasts for 2 seconds (500ms * 4) 9 | export const DEFAULT_COLUMNS_PER_SLICE = 4 10 | 11 | // TODO use this as a vertical zoom level 12 | export const STEP_HEIGHT_COLUNM_RATIO = 1 13 | 14 | export const DEFAULT_NB_TRACKS = 24 // 32 15 | -------------------------------------------------------------------------------- /packages/broadway/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | DEFAULT_DURATION_IN_MS_PER_STEP, 3 | DEFAULT_COLUMNS_PER_STEP, 4 | DEFAULT_COLUMNS_PER_SLICE, 5 | STEP_HEIGHT_COLUNM_RATIO, 6 | DEFAULT_NB_TRACKS, 7 | } from "./general" 8 | 9 | 10 | export { mockCategoryPrompts_misc, mockCategoryPrompts } from "./mocks" 11 | 12 | export { 13 | screenplaySequenceTypes, 14 | type ScreenplaySequenceType, 15 | screenplaySequenceTimes, 16 | type ScreenplaySequenceTime 17 | } from "./screenplaySequences" 18 | 19 | -------------------------------------------------------------------------------- /packages/broadway/src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@datagica/parse-entities'; 2 | declare module '@datagica/parse-names'; -------------------------------------------------------------------------------- /packages/broadway/src/factories/index.ts: -------------------------------------------------------------------------------- 1 | export { createSegment } from "./createSegment" 2 | export { generateClap } from "./generateClap" -------------------------------------------------------------------------------- /packages/broadway/src/parsers/age/index.ts: -------------------------------------------------------------------------------- 1 | export function parseAge() { 2 | 3 | } -------------------------------------------------------------------------------- /packages/broadway/src/parsers/costumes/database.ts: -------------------------------------------------------------------------------- 1 | import { NamedEntity } from "@/types" 2 | 3 | export const data: NamedEntity[] = [ 4 | { 5 | "label": "futuristic", 6 | "aliases": { 7 | "en": [ 8 | ] 9 | }, 10 | prompts: { 11 | CHARACTER: [], 12 | LOCATION: [], 13 | TRANSITION: [], 14 | TIME: [], 15 | ERA: [], 16 | LIGHTING: [], 17 | WEATHER: [], 18 | ACTION: [], 19 | MUSIC: [], 20 | SOUND: [], 21 | DIALOGUE: [], 22 | STYLE: [], 23 | CAMERA: [], 24 | } 25 | } 26 | ] -------------------------------------------------------------------------------- /packages/broadway/src/parsers/eras/index.ts: -------------------------------------------------------------------------------- 1 | import { NamedEntity } from "@/types" 2 | import { createOccurrenceCounter, createSimpleParser, getParserItemFromLabel } from "@/parsers/utils" 3 | 4 | import { data } from "./database" 5 | 6 | export const parseEras = createSimpleParser(data, ["aliases"]) 7 | 8 | export const getMostProbableEras = createOccurrenceCounter(parseEras) 9 | 10 | export const getEra = getParserItemFromLabel(data) -------------------------------------------------------------------------------- /packages/broadway/src/parsers/genres/index.ts: -------------------------------------------------------------------------------- 1 | import { NamedEntity } from "@/types" 2 | import { createOccurrenceCounter, createSimpleParser, getParserItemFromLabel } from "@/parsers/utils" 3 | 4 | import { data } from "./database" 5 | 6 | export const parseGenres = createSimpleParser(data, ["aliases"]) 7 | 8 | export const getMostProbableGenres = createOccurrenceCounter(parseGenres) 9 | 10 | export const getGenre = getParserItemFromLabel(data) -------------------------------------------------------------------------------- /packages/broadway/src/parsers/lights/index.ts: -------------------------------------------------------------------------------- 1 | import { NamedEntity } from "@/types" 2 | import { createSimpleParser, getParserItemFromLabel } from "@/parsers/utils" 3 | 4 | import { data } from "./database" 5 | 6 | export const parseLights = createSimpleParser(data, ["aliases"]) 7 | 8 | export const getLight = getParserItemFromLabel(data) -------------------------------------------------------------------------------- /packages/broadway/src/parsers/locations/locations.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | // TODO use a transition type for the values, to avoid mistakes 4 | 5 | import { ScreenplaySequenceType } from "@/constants/screenplaySequences" 6 | 7 | // TODO: those need to be prefixed by space to avoid false positives 8 | export const locationTypes: Record = { 9 | "INT.": "INTERIOR", 10 | "INT": "INTERIOR", 11 | "INT-": "INTERIOR", 12 | "EXT.": "EXTERIOR", 13 | "EXT": "EXTERIOR", 14 | "EXT-": "EXTERIOR", 15 | "INT./EXT.": "INT./EXT.", 16 | "EXT./INT." : "INT./EXT.", 17 | "INT/EXT" : "INT./EXT.", 18 | "EXT/INT": "INT./EXT.", 19 | "FLASHBACK": "INT./EXT." 20 | } 21 | -------------------------------------------------------------------------------- /packages/broadway/src/parsers/shots/index.ts: -------------------------------------------------------------------------------- 1 | import { NamedEntity } from "@/types" 2 | import { createSimpleParser, getParserItemFromLabel } from "@/parsers/utils" 3 | 4 | import { data } from "./database" 5 | 6 | export const parseShots = createSimpleParser(data, ["aliases"]) 7 | 8 | export const getShot = getParserItemFromLabel(data) -------------------------------------------------------------------------------- /packages/broadway/src/parsers/sounds/index.ts: -------------------------------------------------------------------------------- 1 | import { NamedEntity } from "@/types" 2 | import { createSimpleParser, getParserItemFromLabel } from "@/parsers/utils" 3 | 4 | import { data } from "./database" 5 | 6 | export const parseSounds = createSimpleParser(data) 7 | 8 | export const getSound = getParserItemFromLabel(data) -------------------------------------------------------------------------------- /packages/broadway/src/parsers/transitions/index.ts: -------------------------------------------------------------------------------- 1 | export { parseTransition } from "./parseTransition" 2 | export { transitions } from "./transitions" -------------------------------------------------------------------------------- /packages/broadway/src/parsers/transitions/parseTransition.ts: -------------------------------------------------------------------------------- 1 | import { transitions } from "./transitions" 2 | 3 | export function parseTransition(originalLine: string): string { 4 | 5 | const line = ` ${originalLine.trim()} ` 6 | 7 | // trimmed line is too short to be a transition 8 | if (line.length < 3) { 9 | return "" 10 | } 11 | 12 | for (const transitionKeyword of Object.keys(transitions)) { 13 | if (line.includes(transitionKeyword)) { 14 | return transitionKeyword 15 | } 16 | } 17 | 18 | return "" 19 | } 20 | -------------------------------------------------------------------------------- /packages/broadway/src/parsers/utils/createParser.ts: -------------------------------------------------------------------------------- 1 | import ParseEntities from "@datagica/parse-entities" 2 | 3 | import { NamedEntity, NamedEntityParser, NamedEntityResult } from "@/types" 4 | 5 | export function createParser( 6 | data: NamedEntity[] = [], 7 | fields = ['label','aliases'] 8 | ): NamedEntityParser { 9 | class Parser extends ParseEntities { 10 | constructor() { super({ fields, data }) } 11 | } 12 | const parser = new Parser() 13 | 14 | return (input: string): Promise => ( 15 | (parser.parse(input) as Promise) 16 | ) 17 | } -------------------------------------------------------------------------------- /packages/broadway/src/parsers/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { createOccurrenceCounter } from "./createOccurrenceCounter" 2 | export { createParser } from "./createParser" 3 | export { createSimpleParser } from "./createSimpleParser" 4 | export { getParserItemFromLabel } from "./getParserItemFromLabel" -------------------------------------------------------------------------------- /packages/broadway/src/parsers/weather/index.ts: -------------------------------------------------------------------------------- 1 | import { NamedEntity } from "@/types" 2 | import { createSimpleParser, getParserItemFromLabel } from "@/parsers/utils" 3 | 4 | import { data } from "./database" 5 | 6 | export const parseWeather = createSimpleParser(data, ["aliases"]) 7 | 8 | export const getWeather = getParserItemFromLabel(data) -------------------------------------------------------------------------------- /packages/broadway/src/utils/cleanUTF8Characters.ts: -------------------------------------------------------------------------------- 1 | export function cleanUTF8Characters(input: string): string { 2 | return input.replaceAll("–", "-").replaceAll(" ", "") 3 | } -------------------------------------------------------------------------------- /packages/broadway/src/utils/deduplicate.ts: -------------------------------------------------------------------------------- 1 | export function deduplicate(items: string[]): string[] { 2 | return Object.keys(items.reduce((acc, item) => ({ ...acc, [item]: item }), {})) 3 | } -------------------------------------------------------------------------------- /packages/broadway/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { cleanUTF8Characters } from "./cleanUTF8Characters" 2 | export { deduplicate } from "./deduplicate" 3 | export { getEntities } from "./getEntities" 4 | export { isAllCaps } from "./isAllCaps" 5 | export { onlyContainsStrangeNumber } from "./onlyContainsStrangeNumber" 6 | export { pick } from "./pick" 7 | export { sleep } from "./sleep" 8 | -------------------------------------------------------------------------------- /packages/broadway/src/utils/isAllCaps.ts: -------------------------------------------------------------------------------- 1 | export function isAllCaps(line: string): boolean { 2 | return line === line.toUpperCase() 3 | } -------------------------------------------------------------------------------- /packages/broadway/src/utils/onlyContainsStrangeNumber.ts: -------------------------------------------------------------------------------- 1 | // used to detect anomalies such as lines with only "3." "42." in it, 2 | // possibly indicating a chapter or something 3 | export function onlyContainsStrangeNumber(text: string) { 4 | return text.match(/\S*\d+\.\S*/gi) 5 | } -------------------------------------------------------------------------------- /packages/broadway/src/utils/pick.ts: -------------------------------------------------------------------------------- 1 | export function pick(items: T[]) { 2 | return items[Math.floor(Math.random()*items.length)] 3 | } 4 | -------------------------------------------------------------------------------- /packages/broadway/src/utils/sleep.ts: -------------------------------------------------------------------------------- 1 | export const sleep = async (durationInMs: number) => 2 | new Promise((resolve) => { 3 | setTimeout(() => { 4 | resolve(true) 5 | }, durationInMs) 6 | }) -------------------------------------------------------------------------------- /packages/broadway/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | }, 10 | "include": [ 11 | "src/**/*.ts", 12 | "src/tests/bun-shims.js" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/clap/.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # Except the dist directory 4 | !dist/ 5 | -------------------------------------------------------------------------------- /packages/clap/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/clap/src/constants/defaultValues.ts: -------------------------------------------------------------------------------- 1 | import { ClapImageRatio } from "@/types" 2 | 3 | export const defaultImageRatio = ClapImageRatio.LANDSCAPE -------------------------------------------------------------------------------- /packages/clap/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { defaultImageRatio } from "@/constants/defaultValues" -------------------------------------------------------------------------------- /packages/clap/src/converters/clapToDataUri.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ClapProject } from "@/types" 3 | import { serializeClap } from "@/io/serializeClap" 4 | import { blobToDataUri } from "@/converters/blobToDataUri" 5 | 6 | export async function clapToDataUri(clap: ClapProject): Promise { 7 | const archive = await serializeClap(clap) 8 | const dataUri = await blobToDataUri(archive, "application/x-gzip") 9 | return dataUri 10 | } -------------------------------------------------------------------------------- /packages/clap/src/converters/dataUriToBlob.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Convert a Data URI to a Blob 4 | * 5 | * @param dataURI 6 | * @param defaultContentType (Optional) you can pass a default content 7 | * @returns 8 | */ 9 | export function dataUriToBlob(dataURI = "", defaultContentType = ""): Blob { 10 | dataURI = dataURI.replace(/^data:/, ''); 11 | 12 | const type = dataURI.match(/(?:image|application|video|audio|text)\/[^;]+/)?.[0] || defaultContentType; 13 | const base64 = dataURI.replace(/^[^,]+,/, ''); 14 | const arrayBuffer = new ArrayBuffer(base64.length); 15 | const typedArray = new Uint8Array(arrayBuffer); 16 | 17 | for (let i = 0; i < base64.length; i++) { 18 | typedArray[i] = base64.charCodeAt(i); 19 | } 20 | 21 | return new Blob([arrayBuffer], { type }); 22 | } -------------------------------------------------------------------------------- /packages/clap/src/converters/index.ts: -------------------------------------------------------------------------------- 1 | export { blobToDataUri } from '@/converters/blobToDataUri' 2 | export { clapToDataUri } from '@/converters/clapToDataUri' 3 | export { dataUriToBlob } from '@/converters/dataUriToBlob' 4 | -------------------------------------------------------------------------------- /packages/clap/src/factories/index.ts: -------------------------------------------------------------------------------- 1 | export { newClap } from '@/factories/newClap' 2 | export { newEntity } from '@/factories/newEntity' 3 | export { newSegment } from '@/factories/newSegment' 4 | export { newWorkflow } from '@/factories/newWorkflow' 5 | -------------------------------------------------------------------------------- /packages/clap/src/helpers/README.md: -------------------------------------------------------------------------------- 1 | Utilities to help developers getting quickly bootstrapped -------------------------------------------------------------------------------- /packages/clap/src/helpers/buildEntityIndex.ts: -------------------------------------------------------------------------------- 1 | import { ClapEntity } from "@/types" 2 | 3 | export function buildEntityIndex(entities: ClapEntity[] = []): Record { 4 | const index: Record = {} 5 | for (const entity of entities) { 6 | index[entity.id] = entity 7 | } 8 | return index 9 | } -------------------------------------------------------------------------------- /packages/clap/src/helpers/getEmptyClap.ts: -------------------------------------------------------------------------------- 1 | import { newClap } from "@/factories/newClap" 2 | import { serializeClap } from "@/io/serializeClap" 3 | 4 | let globalState: { 5 | blob?: Blob 6 | } = { 7 | blob: undefined 8 | } 9 | 10 | export async function getEmptyClap(): Promise { 11 | if (globalState.blob) { return globalState.blob } 12 | 13 | const clap = newClap() 14 | 15 | globalState.blob = await serializeClap(clap) 16 | 17 | return globalState.blob 18 | } -------------------------------------------------------------------------------- /packages/clap/src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export { buildEntityIndex } from '@/helpers/buildEntityIndex' 2 | export { filterAssets } from '@/helpers/filterAssets' 3 | export { filterSegmentsByCategory } from '@/helpers/filterSegmentsByCategory' 4 | export { getEmptyClap } from '@/helpers/getEmptyClap' 5 | export { generateClapFromSimpleStory } from '@/helpers/generateClapFromSimpleStory' 6 | export { removeGeneratedAssetUrls } from '@/helpers/removeGeneratedAssetUrls' -------------------------------------------------------------------------------- /packages/clap/src/io/fetchClap.ts: -------------------------------------------------------------------------------- 1 | import { ClapProject } from "@/types" 2 | import { parseClap } from "@/io/parseClap" 3 | 4 | export async function fetchClap(url: string, { 5 | method = "GET", 6 | body, 7 | headers, 8 | cache, 9 | }: { 10 | method?: string 11 | body?: BodyInit | null 12 | headers?: HeadersInit 13 | cache?: RequestCache 14 | } = { 15 | method: "GET" 16 | }): Promise { 17 | 18 | const res = await fetch(url, { 19 | method, 20 | headers, 21 | body, 22 | cache, 23 | }) 24 | 25 | const blob = await res.blob() 26 | 27 | const clap = await parseClap(blob) 28 | 29 | return clap 30 | } -------------------------------------------------------------------------------- /packages/clap/src/io/index.ts: -------------------------------------------------------------------------------- 1 | export { parseClap } from '@/io/parseClap' 2 | export { serializeClap } from '@/io/serializeClap' 3 | export { fetchClap } from '@/io/fetchClap' 4 | export { updateClap } from '@/io/updateClap' 5 | -------------------------------------------------------------------------------- /packages/clap/src/sanitizers/index.ts: -------------------------------------------------------------------------------- 1 | export { sanitizeEntities } from "@/sanitizers/sanitizeEntities" 2 | export { sanitizeEntity } from "@/sanitizers/sanitizeEntity" 3 | export { sanitizeMeta } from "@/sanitizers/sanitizeMeta" 4 | export { sanitizeSegment } from "@/sanitizers/sanitizeSegment" 5 | export { sanitizeSegments } from "@/sanitizers/sanitizeSegments" 6 | export { sanitizeWorkflow } from "@/sanitizers/sanitizeWorkflow" 7 | export { sanitizeWorkflows } from "@/sanitizers/sanitizeWorkflows" -------------------------------------------------------------------------------- /packages/clap/src/sanitizers/sanitizeEntities.ts: -------------------------------------------------------------------------------- 1 | import { ClapEntity } from "@/types"; 2 | import { sanitizeEntity } from "@/sanitizers/sanitizeEntity"; 3 | 4 | export function sanitizeEntities(maybeEntities: ClapEntity[] = []): ClapEntity[] { 5 | return maybeEntities.map(entity => sanitizeEntity(entity)) 6 | } -------------------------------------------------------------------------------- /packages/clap/src/sanitizers/sanitizeSegments.ts: -------------------------------------------------------------------------------- 1 | import { ClapSegment } from "@/types"; 2 | import { sanitizeSegment } from "@/sanitizers/sanitizeSegment"; 3 | 4 | export function sanitizeSegments(maybeSegments: ClapSegment[] = []): ClapSegment[] { 5 | return maybeSegments.map(segment => sanitizeSegment(segment)) 6 | } -------------------------------------------------------------------------------- /packages/clap/src/sanitizers/sanitizeWorkflows.ts: -------------------------------------------------------------------------------- 1 | import { ClapWorkflow } from "@/types"; 2 | import { sanitizeWorkflow } from "@/sanitizers/sanitizeWorkflow"; 3 | 4 | export function sanitizeWorkflows(maybeWorkflows: ClapWorkflow[] = []): ClapWorkflow[] { 5 | return maybeWorkflows.map(workflow => sanitizeWorkflow(workflow)) 6 | } -------------------------------------------------------------------------------- /packages/clap/src/utils/generateSeed.ts: -------------------------------------------------------------------------------- 1 | export function generateSeed() { 2 | return Math.floor(Math.random() * Math.pow(2, 31)); 3 | } -------------------------------------------------------------------------------- /packages/clap/src/utils/getClapAssetSourceType.ts: -------------------------------------------------------------------------------- 1 | import { ClapAssetSource } from "@/types" 2 | 3 | export function getClapAssetSourceType(input: string = ""): ClapAssetSource { 4 | 5 | const str = `${input || ""}`.trim() 6 | 7 | if (!str || !str.length) { 8 | return ClapAssetSource.EMPTY 9 | } 10 | 11 | if (str.startsWith("https://") || str.startsWith("http://")) { 12 | return ClapAssetSource.REMOTE 13 | } 14 | 15 | // note that "path" assets are potentially a security risk, they need to be treated with care 16 | if (str.startsWith("/") || str.startsWith("../") || str.startsWith("./")) { 17 | return ClapAssetSource.PATH 18 | } 19 | 20 | if (str.startsWith("data:")) { 21 | return ClapAssetSource.DATA 22 | } 23 | 24 | return ClapAssetSource.PROMPT 25 | } -------------------------------------------------------------------------------- /packages/clap/src/utils/getValidNumber.ts: -------------------------------------------------------------------------------- 1 | export const getValidNumber = (something: any, minValue: number, maxValue: number, defaultValue: number) => { 2 | const strValue = `${something || defaultValue}` 3 | const numValue = Number(strValue) 4 | const isValid = !isNaN(numValue) && isFinite(numValue) 5 | if (!isValid) { 6 | return defaultValue 7 | } 8 | return Math.max(minValue, Math.min(maxValue, numValue)) 9 | 10 | } -------------------------------------------------------------------------------- /packages/clap/src/utils/isValidNumber.ts: -------------------------------------------------------------------------------- 1 | export function isValidNumber(input?: any) { 2 | return typeof input === "number" && !isNaN(input) && isFinite(input) 3 | } -------------------------------------------------------------------------------- /packages/clap/src/utils/parseWorkflowCategory.ts: -------------------------------------------------------------------------------- 1 | import { ClapWorkflowCategory } from "@/types" 2 | 3 | export function parseWorkflowCategory(input: any, defaultToUse?: ClapWorkflowCategory): ClapWorkflowCategory { 4 | 5 | let unknownString = `${input || ""}`.trim() 6 | 7 | // the "normal" case 8 | if (Object.values(ClapWorkflowCategory).includes(unknownString as ClapWorkflowCategory)) { 9 | return unknownString as ClapWorkflowCategory 10 | } 11 | 12 | let category: ClapWorkflowCategory = defaultToUse || ClapWorkflowCategory.IMAGE_GENERATION 13 | 14 | return category 15 | } -------------------------------------------------------------------------------- /packages/clap/src/utils/parseWorkflowProvider.ts: -------------------------------------------------------------------------------- 1 | import { ClapWorkflowProvider } from "@/types" 2 | 3 | export function parseWorkflowProvider(input: any, defaultToUse?: ClapWorkflowProvider): ClapWorkflowProvider { 4 | 5 | let unknownString = `${input || ""}`.trim() 6 | 7 | // the "normal" case 8 | if (Object.values(ClapWorkflowProvider).includes(unknownString as ClapWorkflowProvider)) { 9 | return unknownString as ClapWorkflowProvider 10 | } 11 | 12 | let provider: ClapWorkflowProvider = defaultToUse || ClapWorkflowProvider.NONE 13 | 14 | return provider 15 | } -------------------------------------------------------------------------------- /packages/clap/src/utils/uuid.ts: -------------------------------------------------------------------------------- 1 | import PureUUID from "pure-uuid" 2 | 3 | export function UUID() { 4 | return new PureUUID(4).format() 5 | } -------------------------------------------------------------------------------- /packages/clap/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "plugins": [{ "transform": "typescript-transform-paths", "afterDeclarations": true }] 10 | }, 11 | "include": ["src/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/clapper-services/.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # Except the dist directory 4 | !dist/ 5 | -------------------------------------------------------------------------------- /packages/clapper-services/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/clapper-services/README.md: -------------------------------------------------------------------------------- 1 | # @aitube/clapper-services 2 | 3 | *Base types for Clapper services* 4 | 5 | This module is used by Clapper and by Clapper plugins 6 | 7 | -------------------------------------------------------------------------------- /packages/clapper-services/src/broadcast.ts: -------------------------------------------------------------------------------- 1 | 2 | export type BroadcastState = { 3 | isBroadcasting: boolean 4 | } 5 | 6 | export type BroadcastControls = { 7 | /** 8 | * Toggles the broadcast on or off 9 | * @param forceValue 10 | * @returns 11 | */ 12 | toggleBroadcast: (forceValue?: boolean) => boolean 13 | } 14 | 15 | export type BroadcastStore = BroadcastState & BroadcastControls -------------------------------------------------------------------------------- /packages/clapper-services/src/editors.ts: -------------------------------------------------------------------------------- 1 | export enum EditorView { 2 | PROJECT = "PROJECT", 3 | SCRIPT = "SCRIPT", 4 | ENTITY = "ENTITY", 5 | SEGMENT = "SEGMENT", 6 | HISTORY = "HISTORY", 7 | WORKFLOW = "WORKFLOW", 8 | } 9 | 10 | export type EditorsState = { 11 | view: EditorView 12 | } 13 | 14 | export type EditorsControls = { 15 | setView: (editorView: EditorView) => void 16 | } 17 | 18 | export type EditorsStore = EditorsState & EditorsControls 19 | -------------------------------------------------------------------------------- /packages/clapper-services/src/entity-editor.ts: -------------------------------------------------------------------------------- 1 | import { ClapEntity } from "@aitube/clap" 2 | 3 | import { VersionControls, VersionState } from "./version-control" 4 | 5 | export type EntityEditorState = { 6 | draft?: ClapEntity 7 | showEntityList: boolean 8 | } & VersionState 9 | 10 | export type EntityEditorControls = { 11 | setDraft: (draft?: ClapEntity) => void 12 | selectEntity: (id: string) => void 13 | addEntity: (entity: ClapEntity) => void 14 | removeEntity: (id: string) => void 15 | setShowEntityList: (showEntityList: boolean) => void 16 | } & VersionControls 17 | 18 | export type EntityEditorStore = EntityEditorState & EntityEditorControls 19 | -------------------------------------------------------------------------------- /packages/clapper-services/src/mic.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Mic 3 | */ 4 | export type MicState = { 5 | isSupported: boolean 6 | isListening: boolean 7 | transcript: string 8 | interimResults: boolean 9 | error: string 10 | lang: string 11 | grammar: string 12 | grammarWeight: number 13 | continuous: boolean 14 | recognition?: SpeechRecognition 15 | } 16 | export type MicControls = { 17 | init: () => void 18 | start: () => void 19 | stop: () => void 20 | clear: () => void 21 | } 22 | 23 | export type MicStore = 24 | MicState & 25 | MicControls 26 | -------------------------------------------------------------------------------- /packages/clapper-services/src/project-editor.ts: -------------------------------------------------------------------------------- 1 | import { ClapMeta } from "@aitube/clap" 2 | 3 | import { VersionControls, VersionState } from "./version-control" 4 | 5 | export type ProjectEditorState = { 6 | 7 | } & VersionState 8 | 9 | 10 | export type ProjectEditorControls = { 11 | 12 | } & VersionControls 13 | 14 | export type ProjectEditorStore = ProjectEditorState & ProjectEditorControls 15 | -------------------------------------------------------------------------------- /packages/clapper-services/src/segment-editor.ts: -------------------------------------------------------------------------------- 1 | import { TimelineSegment } from "@aitube/timeline" 2 | 3 | import { VersionControls, VersionState } from "./version-control" 4 | 5 | export type SegmentEditorState = { 6 | 7 | } & VersionState 8 | 9 | export type SegmentEditorControls = { 10 | 11 | } & VersionControls 12 | 13 | export type SegmentEditorStore = SegmentEditorState & SegmentEditorControls 14 | -------------------------------------------------------------------------------- /packages/clapper-services/src/simulator.ts: -------------------------------------------------------------------------------- 1 | export type SimulatorState = { 2 | isRunning: boolean 3 | } 4 | 5 | export type SimulatorControls = { 6 | startLoop: () => void 7 | runLoop: () => Promise 8 | 9 | togglePause: (isPaused?: boolean) => boolean 10 | } 11 | 12 | export type SimulatorStore = SimulatorState & SimulatorControls -------------------------------------------------------------------------------- /packages/clapper-services/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | }, 10 | "include": [ 11 | "src/**/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/client/.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # Except the dist directory 4 | !dist/ 5 | -------------------------------------------------------------------------------- /packages/client/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/client/src/api/index.ts: -------------------------------------------------------------------------------- 1 | export { createClap } from "./createClap" 2 | export { editClapDialogues } from "./editClapDialogues" 3 | export { editClapEntities } from "./editClapEntities" 4 | export { editClapMusic } from "./editClapMusic" 5 | export { editClapSounds } from "./editClapSounds" 6 | export { editClapStory } from "./editClapStory" 7 | export { editClapStoryboards } from "./editClapStoryboards" 8 | export { editClapVideos } from "./editClapVideos" 9 | export { exportClapToVideo } from "./exportClapToVideo" 10 | -------------------------------------------------------------------------------- /packages/client/src/constants/config.ts: -------------------------------------------------------------------------------- 1 | import { defaultAitubeHostname } from "./defaultValues" 2 | 3 | // we leave the opportunity to override this at runtime 4 | export const aitubeUrl = `${ 5 | process.env.AITUBE_URL || 6 | `https://${defaultAitubeHostname}` 7 | }` 8 | 9 | // note: let's keep it simple and only support one version at a time 10 | export const aitubeApiVersion = "v1" 11 | 12 | export const aitubeApiUrl = `${ 13 | aitubeUrl 14 | }${ 15 | aitubeUrl.endsWith("/") ? "" : "/" 16 | }api/${aitubeApiVersion}/` 17 | -------------------------------------------------------------------------------- /packages/client/src/constants/defaultValues.ts: -------------------------------------------------------------------------------- 1 | // unfortunately, this doesn't work yet due to a redirection issue 2 | // const defaultAitubeHostname = "aitube.at" 3 | 4 | // so we have to use the direct space hostname instead 5 | export const defaultAitubeHostname = "aitube.at" 6 | 7 | export const defaultClapWidth = 512 8 | export const defaultClapHeight = 288 9 | 10 | export const defaultExportFormat = "mp4" -------------------------------------------------------------------------------- /packages/client/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | defaultAitubeHostname, 3 | defaultClapWidth, 4 | defaultClapHeight, 5 | defaultExportFormat 6 | } from './defaultValues' 7 | 8 | export { 9 | aitubeUrl, 10 | aitubeApiVersion, 11 | aitubeApiUrl 12 | } from './config' 13 | 14 | export { 15 | ClapEntityPrompt, 16 | SupportedExportFormat 17 | } from "./types" 18 | -------------------------------------------------------------------------------- /packages/client/src/constants/types.ts: -------------------------------------------------------------------------------- 1 | import { ClapSegmentCategory } from "@aitube/clap" 2 | 3 | export type SupportedExportFormat = "mp4" | "webm" 4 | 5 | export type ClapEntityPrompt = { 6 | name: string 7 | 8 | // eg. "character", "location" 9 | category: ClapSegmentCategory 10 | 11 | // age of the person, animal or entity (eg. robot, talking spaceship etc) 12 | age: string 13 | 14 | // characterization of the person, animal or entity (texture, hair color, gender etc) 15 | variant: string 16 | 17 | // region from where the person, animal or entity is coming from (human, mechanical, alien planet, european, south-american etc) 18 | region: string 19 | 20 | // identity picture 21 | identityImage: string 22 | 23 | // identity voice 24 | identityVoice: string 25 | } -------------------------------------------------------------------------------- /packages/client/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { 3 | createClap, 4 | editClapDialogues, 5 | editClapEntities, 6 | editClapMusic, 7 | editClapSounds, 8 | editClapStory, 9 | editClapStoryboards, 10 | editClapVideos, 11 | exportClapToVideo, 12 | } from './api' 13 | 14 | export { 15 | defaultAitubeHostname, 16 | defaultClapWidth, 17 | defaultClapHeight, 18 | defaultExportFormat, 19 | aitubeUrl, 20 | aitubeApiVersion, 21 | aitubeApiUrl, 22 | ClapEntityPrompt, 23 | SupportedExportFormat 24 | } from "./constants" 25 | 26 | export { 27 | applyClapCompletion, 28 | } from "./utils" -------------------------------------------------------------------------------- /packages/client/src/parsers/index.ts: -------------------------------------------------------------------------------- 1 | export { parseEntityPrompt } from "./parseEntityPrompt" 2 | export { parseString } from "./parseString" 3 | export { parseStringArray } from "./parseStringArray" -------------------------------------------------------------------------------- /packages/client/src/parsers/parseString.ts: -------------------------------------------------------------------------------- 1 | export function parseString(input?: any, defaultValue?: string): string { 2 | const defValue = `${defaultValue || ""}` 3 | 4 | if (typeof input !== "string") { return defValue } 5 | 6 | return input || defValue 7 | } -------------------------------------------------------------------------------- /packages/client/src/parsers/parseStringArray.ts: -------------------------------------------------------------------------------- 1 | export function parseStringArray(something: any): string[] { 2 | let result: string[] = [] 3 | if (typeof something === "string") { 4 | result = [something] 5 | } else if (Array.isArray(something)) { 6 | result = something.map(thing => typeof thing === "string" ? thing : "").filter(x => x) 7 | } 8 | return result 9 | } -------------------------------------------------------------------------------- /packages/client/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { applyClapCompletion } from "./applyClapCompletion" 2 | -------------------------------------------------------------------------------- /packages/client/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | }, 10 | "include": [ 11 | "src/**/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/colors/.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # Except the dist directory 4 | !dist/ 5 | -------------------------------------------------------------------------------- /packages/colors/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/colors/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { segmentColorsTailwind, type SegmentColor } from "./colors" 2 | 3 | export { 4 | segmentCategories, 5 | categoryNames, 6 | getSegment, 7 | getSegmentColor 8 | } from "./segments" 9 | -------------------------------------------------------------------------------- /packages/colors/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { 3 | segmentColorsTailwind, type SegmentColor, 4 | segmentCategories, 5 | categoryNames, 6 | getSegment, 7 | getSegmentColor 8 | } from "./constants" 9 | 10 | export { 11 | type ClapSegmentCategorySettings 12 | } from "./types" 13 | -------------------------------------------------------------------------------- /packages/colors/src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | // import { ClapSegmentCategory } from "@aitube/clap" 3 | 4 | import { SegmentColor } from "./constants/colors" 5 | 6 | export interface ClapSegmentCategorySettings { 7 | id: any // ClapSegmentCategory // eg. "country" 8 | title: string // eg. Country 9 | description: string // description in one sentence 10 | color: SegmentColor // base color name, in lowercase 11 | } -------------------------------------------------------------------------------- /packages/colors/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | }, 10 | "include": [ 11 | "src/**/*.ts", 12 | "src/tests/bun-shims.js" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/engine/.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # Except the dist directory 4 | !dist/ 5 | -------------------------------------------------------------------------------- /packages/engine/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/engine/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | renderShotToStoryboard, 3 | renderShotToVideo 4 | } from './renderers' 5 | 6 | export { 7 | getBackgroundAudioPrompt, 8 | getCharacterPrompt, 9 | getCharacterReferencePrompt, 10 | getMusicPrompt, 11 | getPositivePrompt, 12 | getSoundPrompt, 13 | getSpeechBackgroundAudioPrompt, 14 | getSpeechForegroundAudioPrompt, 15 | getVideoPrompt, 16 | segmentCategoryPromptPriority, 17 | } from './prompts' 18 | 19 | export { 20 | deduplicate, 21 | deduplicatePrompt, 22 | } from './utils' 23 | 24 | export { 25 | VideoRenderer, 26 | StoryboardRenderer, 27 | VideoFirstFrameExtractor 28 | } from "./types" -------------------------------------------------------------------------------- /packages/engine/src/prompts/getCharacterPrompt.ts: -------------------------------------------------------------------------------- 1 | import { ClapEntity } from "@aitube/clap" 2 | 3 | export function getCharacterPrompt(entity: ClapEntity): string { 4 | const characterPrompt = [ 5 | entity.age ? `${entity.age}yo` : '', // 34yo 6 | entity.region ? `${entity.region}` : '', // american 7 | entity.gender !== "object" ? entity.gender : 'person', // woman 8 | entity.label ? `named ${entity.label}` : '', // Jessica 9 | entity.appearance ? `${entity.appearance}` : 'speaking', // blond hair 10 | // entity.description ? `${entity.description}` : '', // blond hair 11 | ].map(i => i.trim()).filter(i => i).join(" ").trim() 12 | 13 | return characterPrompt 14 | } -------------------------------------------------------------------------------- /packages/engine/src/prompts/getMusicPrompt.ts: -------------------------------------------------------------------------------- 1 | import { ClapSegment, ClapSegmentCategory } from "@aitube/clap" 2 | 3 | export function getMusicPrompt( 4 | segments: ClapSegment[] = [] 5 | ): string { 6 | return segments 7 | .filter(({ category }) => category === ClapSegmentCategory.MUSIC) 8 | .sort((a, b) => b.label.localeCompare(a.label)) 9 | .map(({ prompt }) => prompt).filter(x => x) 10 | .join(". ") 11 | } -------------------------------------------------------------------------------- /packages/engine/src/prompts/getSoundPrompt.ts: -------------------------------------------------------------------------------- 1 | import { ClapSegment, ClapSegmentCategory } from "@aitube/clap" 2 | 3 | /** 4 | * 5 | * @param segments 6 | * @returns 7 | */ 8 | export function getSoundPrompt( 9 | segments: ClapSegment[] = [], 10 | extraPositivePrompt: string[] = [] // "clear sound, high quality" etc 11 | ): string { 12 | return segments 13 | .filter(({ category }) => ( 14 | category === ClapSegmentCategory.SOUND 15 | )) 16 | .sort((a, b) => b.label.localeCompare(a.label)) 17 | .map(segment => { 18 | return segment.prompt 19 | }) 20 | .filter(x => x) 21 | .concat([ ...extraPositivePrompt ]) 22 | .join(". ") 23 | } -------------------------------------------------------------------------------- /packages/engine/src/prompts/getSpeechForegroundAudioPrompt.ts: -------------------------------------------------------------------------------- 1 | import { ClapSegment, ClapSegmentCategory } from "@aitube/clap" 2 | 3 | /** 4 | * Construct an audio foreground for a voice from a list of active segments 5 | * 6 | * This is the "dialogue" prompt, ie. the actual spoken words, 7 | * so we don't need to do anything fancy here, we only use the raw text 8 | * 9 | * @param segments 10 | * @returns 11 | */ 12 | export function getSpeechForegroundAudioPrompt( 13 | segments: ClapSegment[] = [] 14 | ): string { 15 | return segments 16 | .filter(({ category }) => category === ClapSegmentCategory.DIALOGUE) 17 | .sort((a, b) => b.label.localeCompare(a.label)) 18 | .map(({ prompt }) => prompt).filter(x => x) 19 | .join(". ") 20 | } -------------------------------------------------------------------------------- /packages/engine/src/prompts/index.ts: -------------------------------------------------------------------------------- 1 | export { getBackgroundAudioPrompt } from "./getBackgroundAudioPrompt" 2 | export { getCharacterPrompt } from "./getCharacterPrompt" 3 | export { getCharacterReferencePrompt } from "./getCharacterReferencePrompt" 4 | export { getMusicPrompt } from './getMusicPrompt' 5 | export { getPositivePrompt } from "./getPositivePrompt" 6 | export { getSoundPrompt } from "./getSoundPrompt" 7 | export { getSpeechBackgroundAudioPrompt } from "./getSpeechBackgroundAudioPrompt" 8 | export { getSpeechForegroundAudioPrompt } from "./getSpeechForegroundAudioPrompt" 9 | export { getVideoPrompt } from "./getVideoPrompt" 10 | export { segmentCategoryPromptPriority } from "./priorities" -------------------------------------------------------------------------------- /packages/engine/src/renderers/index.ts: -------------------------------------------------------------------------------- 1 | export { renderShotToStoryboard } from "./storyboard/renderShotToStoryboard" 2 | export { renderShotToVideo } from "./video/renderShotToVideo" -------------------------------------------------------------------------------- /packages/engine/src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export type StoryboardRenderer = (request: { 3 | prompt: string; 4 | identityImage?: string; 5 | width?: number; 6 | height?: number; 7 | seed?: number; 8 | turbo?: boolean; 9 | }) => Promise 10 | 11 | 12 | export type VideoRenderer = (request: { 13 | imageInputBase64?: string; 14 | seed?: number; 15 | width?: number; 16 | height?: number; 17 | nbFrames?: number; 18 | nbFPS?: number; 19 | nbSteps?: number; 20 | debug?: boolean; 21 | }) => Promise 22 | 23 | export type VideoFirstFrameExtractor = (params: { 24 | inputVideo?: string; 25 | outputFormat?: "jpeg" | "png" | "webp"; 26 | }) => Promise 27 | -------------------------------------------------------------------------------- /packages/engine/src/utils/deduplicate.ts: -------------------------------------------------------------------------------- 1 | export function deduplicate(items: string[]): string[] { 2 | return Object.keys(items.reduce((acc, item) => ({ ...acc, [item]: item }), {})) 3 | } -------------------------------------------------------------------------------- /packages/engine/src/utils/deduplicatePrompt.ts: -------------------------------------------------------------------------------- 1 | import { deduplicate } from "@/utils/deduplicate" 2 | 3 | export function deduplicatePrompt(input: string): string { 4 | return deduplicate(input.split(",").map(item => item.trim())).join(", ") 5 | } -------------------------------------------------------------------------------- /packages/engine/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { deduplicate } from "./deduplicate" 2 | export { deduplicatePrompt } from "./deduplicatePrompt" 3 | -------------------------------------------------------------------------------- /packages/engine/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | }, 10 | "include": [ 11 | "src/**/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/io/.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # Except the dist directory 4 | !dist/ 5 | -------------------------------------------------------------------------------- /packages/io/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/io/src/delete/deleteFile.ts: -------------------------------------------------------------------------------- 1 | import { rm } from "node:fs/promises" 2 | 3 | export async function deleteFile(filePath: string, debug?: boolean): Promise { 4 | try { 5 | await rm(filePath, { recursive: true, force: true }) 6 | // await unlink(filePath) 7 | return true 8 | } catch (err) { 9 | if (debug) { 10 | console.error(`failed to unlink file at ${filePath}: ${err}`) 11 | } 12 | } 13 | return false 14 | } -------------------------------------------------------------------------------- /packages/io/src/delete/deleteFilesWithName.ts: -------------------------------------------------------------------------------- 1 | import { readdir } from "node:fs/promises" 2 | import path from "node:path" 3 | 4 | import { deleteFile } from "./deleteFile" 5 | 6 | export const deleteFilesWithName = async (dir: string, name: string, debug?: boolean) => { 7 | // console.log(`deleteFilesWithName(${dir}, ${name})`) 8 | for (const file of await readdir(dir)) { 9 | if (file.includes(name)) { 10 | await deleteFile(path.join(dir, file)) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/io/src/delete/index.ts: -------------------------------------------------------------------------------- 1 | export { deleteFile } from "./deleteFile" 2 | export { deleteFilesWithName } from "./deleteFilesWithName" 3 | export { removeTemporaryFiles } from "./removeTemporaryFiles" -------------------------------------------------------------------------------- /packages/io/src/delete/removeTemporaryFiles.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, promises as fs } from "node:fs" 2 | 3 | // note: this function will never fail 4 | export async function removeTemporaryFiles(filesPaths: string[]) { 5 | try { 6 | // Cleanup temporary files - you could choose to do this or leave it to the user 7 | await Promise.all(filesPaths.map(async (filePath) => { 8 | try { 9 | if (existsSync(filePath)) { 10 | await fs.rm(filePath) 11 | } 12 | } catch (err) { 13 | // 14 | } 15 | })) 16 | } catch (err) { 17 | // no big deal, except a bit of tmp file leak 18 | // although.. if delete failed, it could also indicate 19 | // that the file has already been cleaned-up, so even better! 20 | } 21 | } -------------------------------------------------------------------------------- /packages/io/src/fetch/index.ts: -------------------------------------------------------------------------------- 1 | export { downloadFileAsBase64 } from "./downloadFileAsBase64" -------------------------------------------------------------------------------- /packages/io/src/image/convertImageToOriginal.ts: -------------------------------------------------------------------------------- 1 | 2 | // you are reading it right: this function does.. nothing! 3 | // it is a NOOP conversion function 4 | export async function convertImageToOriginal(imgBase64: string = ""): Promise { 5 | return imgBase64 6 | } -------------------------------------------------------------------------------- /packages/io/src/image/convertImageToPng.ts: -------------------------------------------------------------------------------- 1 | import sharp from "sharp" 2 | 3 | export async function convertImageToPng(imgBase64: string = ""): Promise { 4 | 5 | const base64WithoutHeader = imgBase64.split(";base64,")[1] || "" 6 | 7 | if (!base64WithoutHeader) { 8 | const slice = `${imgBase64 || ""}`.slice(0, 50) 9 | throw new Error(`couldn't process input image "${slice}..."`) 10 | } 11 | 12 | // Convert base64 to buffer 13 | const tmpBuffer = Buffer.from(base64WithoutHeader, 'base64') 14 | 15 | const newBuffer = await sharp(tmpBuffer) 16 | .png() 17 | .toBuffer() 18 | 19 | // Convert the buffer back to base64 20 | const newImageBase64 = newBuffer.toString('base64') 21 | 22 | return `data:image/png;base64,${newImageBase64}` 23 | } -------------------------------------------------------------------------------- /packages/io/src/image/imageFormats.ts: -------------------------------------------------------------------------------- 1 | export type ImageFileExt = "png" | "jpeg" | "jpg" | "webp" 2 | -------------------------------------------------------------------------------- /packages/io/src/image/index.ts: -------------------------------------------------------------------------------- 1 | export { convertImageTo } from "./convertImageTo" 2 | export { convertImageToJpeg } from "./convertImageToJpeg" 3 | export { convertImageToOriginal } from "./convertImageToOriginal" 4 | export { convertImageToPng } from "./convertImageToPng" 5 | export { convertImageToWebp } from "./convertImageToWebp" 6 | export type { ImageFileExt } from "./imageFormats" 7 | export { resizeImage } from "./resizeImage" -------------------------------------------------------------------------------- /packages/io/src/read/index.ts: -------------------------------------------------------------------------------- 1 | export { readJpegFileToBase64 } from "./readJpegFileToBase64" 2 | export { readLocalOrRemotePlainText } from "./readLocalOrRemotePlainText" 3 | export { readMp3FileToBase64 } from "./readMp3FileToBase64" 4 | export { readMp4FileToBase64 } from "./readMp4FileToBase64" 5 | export { readPlainText } from "./readPlainText" 6 | export { readPngFileToBase64 } from "./readPngFileToBase64" 7 | export { readWavFileToBase64 } from "./readWavFileToBase64" 8 | -------------------------------------------------------------------------------- /packages/io/src/read/readJpegFileToBase64.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "node:fs/promises" 2 | 3 | export async function readJpegFileToBase64(filePath: string): Promise { 4 | try { 5 | // Read the file's content as a Buffer 6 | const fileBuffer = await readFile(filePath); 7 | 8 | // Convert the buffer to a base64 string 9 | const base64 = fileBuffer.toString('base64'); 10 | 11 | // Prefix the base64 string with the Data URI scheme for PNG images 12 | return `data:image/jpeg;base64,${base64}`; 13 | } catch (error) { 14 | // Handle errors (e.g., file not found, no permissions, etc.) 15 | console.error(error); 16 | throw error; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/io/src/read/readLocalOrRemotePlainText.ts: -------------------------------------------------------------------------------- 1 | import { readPlainText } from "./readPlainText" 2 | 3 | export async function readLocalOrRemotePlainText(input: string): Promise { 4 | 5 | if (input.startsWith("https://") || input.startsWith("http://")) { 6 | try { 7 | const res = await fetch(input) 8 | return res.text() 9 | } catch (err) { 10 | return input 11 | } 12 | } 13 | 14 | // (probably) too long to be a filepath 15 | if (input.length > 4096) { 16 | return input 17 | } 18 | 19 | if (input.endsWith(".txt") || input.endsWith(".md")) { 20 | try { 21 | return readPlainText(input) 22 | } catch (err) { 23 | return input 24 | } 25 | } 26 | 27 | return input 28 | } -------------------------------------------------------------------------------- /packages/io/src/read/readMp3FileToBase64.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "node:fs/promises" 2 | 3 | export async function readMp3FileToBase64(filePath: string): Promise { 4 | try { 5 | // Read the file's content as a Buffer 6 | const fileBuffer = await readFile(filePath); 7 | 8 | // Convert the buffer to a base64 string 9 | const base64 = fileBuffer.toString('base64'); 10 | 11 | // Prefix the base64 string with the Data URI scheme for PNG images 12 | return `data:audio/mp3;base64,${base64}`; 13 | } catch (error) { 14 | // Handle errors (e.g., file not found, no permissions, etc.) 15 | console.error(error); 16 | throw error; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/io/src/read/readMp4FileToBase64.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "node:fs/promises" 2 | 3 | export async function readMp4FileToBase64(filePath: string): Promise { 4 | try { 5 | // Read the file's content as a Buffer 6 | const fileBuffer = await readFile(filePath) 7 | 8 | // Convert the buffer to a base64 string 9 | const base64 = fileBuffer.toString('base64') 10 | 11 | // Prefix the base64 string with the Data URI scheme for PNG images 12 | return `data:video/mp4;base64,${base64}` 13 | } catch (error) { 14 | // Handle errors (e.g., file not found, no permissions, etc.) 15 | console.error(error) 16 | throw error 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/io/src/read/readPlainText.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "node:fs/promises" 2 | 3 | export async function readPlainText(filePath: string): Promise { 4 | try { 5 | const plainText = await readFile(filePath, "utf-8") 6 | 7 | return plainText 8 | } catch (error) { 9 | // Handle errors (e.g., file not found, no permissions, etc.) 10 | console.error(error) 11 | throw error 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/io/src/read/readPngFileToBase64.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "node:fs/promises" 2 | 3 | export async function readPngFileToBase64(filePath: string): Promise { 4 | try { 5 | // Read the file's content as a Buffer 6 | const fileBuffer = await readFile(filePath) 7 | 8 | // Convert the buffer to a base64 string 9 | const base64 = fileBuffer.toString('base64') 10 | 11 | // Prefix the base64 string with the Data URI scheme for PNG images 12 | return `data:image/png;base64,${base64}` 13 | } catch (error) { 14 | // Handle errors (e.g., file not found, no permissions, etc.) 15 | console.error(error) 16 | throw error 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/io/src/read/readWavFileToBase64.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "node:fs/promises" 2 | 3 | export async function readWavFileToBase64(filePath: string): Promise { 4 | try { 5 | // Read the file's content as a Buffer 6 | const fileBuffer = await readFile(filePath) 7 | 8 | // Convert the buffer to a base64 string 9 | const base64 = fileBuffer.toString('base64') 10 | 11 | // Prefix the base64 string with the Data URI scheme for PNG images 12 | return `data:audio/wav;base64,${base64}` 13 | } catch (error) { 14 | // Handle errors (e.g., file not found, no permissions, etc.) 15 | console.error(error) 16 | throw error 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/io/src/tmp/getRandomDirectory.ts: -------------------------------------------------------------------------------- 1 | import { tmpdir } from "node:os" 2 | import { join } from "node:path" 3 | import { mkdtemp } from "node:fs/promises" 4 | 5 | import { UUID } from "./uuid" 6 | 7 | export async function getRandomDirectory(): Promise { 8 | return mkdtemp(join(tmpdir(), UUID())) 9 | } -------------------------------------------------------------------------------- /packages/io/src/tmp/index.ts: -------------------------------------------------------------------------------- 1 | export { getRandomDirectory } from "./getRandomDirectory" -------------------------------------------------------------------------------- /packages/io/src/tmp/uuid.ts: -------------------------------------------------------------------------------- 1 | import PureUUID from "pure-uuid" 2 | 3 | export function UUID() { 4 | return new PureUUID(4).format() 5 | } -------------------------------------------------------------------------------- /packages/io/src/write/index.ts: -------------------------------------------------------------------------------- 1 | export { writeBase64ToFile } from "./writeBase64ToFile" -------------------------------------------------------------------------------- /packages/io/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | }, 10 | "include": [ 11 | "src/**/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/timeline/.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # Except the dist directory 4 | !dist/ 5 | -------------------------------------------------------------------------------- /packages/timeline/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/timeline/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/timeline/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/timeline/src/components/camera/TimelineCamera.tsx: -------------------------------------------------------------------------------- 1 | import { OrthographicCamera } from "@react-three/drei" 2 | 3 | import { useTimeline } from "@/hooks" 4 | 5 | export function TimelineCamera() { 6 | const setTimelineCamera = useTimeline(s => s.setTimelineCamera) 7 | return ( 8 | { 10 | if (ortographicCamera) { 11 | setTimelineCamera(ortographicCamera) 12 | } 13 | }} 14 | makeDefault 15 | position={[0, 0, 1]} 16 | /> 17 | ) 18 | } -------------------------------------------------------------------------------- /packages/timeline/src/components/camera/index.ts: -------------------------------------------------------------------------------- 1 | export { TimelineCamera } from "./TimelineCamera" 2 | export type { TimelineCameraImpl } from "./types" 3 | -------------------------------------------------------------------------------- /packages/timeline/src/components/camera/types.tsx: -------------------------------------------------------------------------------- 1 | import { OrthographicCamera as OrthographicCameraImpl } from 'three' 2 | 3 | export type TimelineCameraImpl = OrthographicCameraImpl 4 | -------------------------------------------------------------------------------- /packages/timeline/src/components/cells/index.tsx: -------------------------------------------------------------------------------- 1 | export { Cell } from "./Cell" 2 | export { ImageCell } from "./ImageCell" 3 | export { VideoCell } from "./VideoCell" 4 | export { TextCell } from "./TextCell" 5 | export type { SpecializedCellProps } from "./types" 6 | -------------------------------------------------------------------------------- /packages/timeline/src/components/cells/types.ts: -------------------------------------------------------------------------------- 1 | import { ClapTrack } from "@aitube/clap" 2 | 3 | import { ClapSegmentColorScheme, TimelineSegment } from "@/types" 4 | import { SegmentArea } from "@/types/timeline" 5 | 6 | export type SpecializedCellProps = { 7 | segment: TimelineSegment 8 | cellWidth: number 9 | cellHeight: number 10 | isHovered: boolean 11 | setHoveredSegment: (params?: { 12 | segment?: TimelineSegment 13 | area?: SegmentArea 14 | }) => void 15 | durationInSteps: number 16 | startTimeInSteps: number 17 | colorScheme: ClapSegmentColorScheme 18 | widthInPx: number 19 | widthInPxAfterZoom: number 20 | isResizing: boolean 21 | track: ClapTrack 22 | } 23 | -------------------------------------------------------------------------------- /packages/timeline/src/components/controls/index.ts: -------------------------------------------------------------------------------- 1 | export { TimelineControls } from "./TimelineControls" -------------------------------------------------------------------------------- /packages/timeline/src/components/controls/types.ts: -------------------------------------------------------------------------------- 1 | import { MapControls as MapControlsImpl } from 'three-stdlib' 2 | 3 | export type TimelineControlsImpl = MapControlsImpl 4 | -------------------------------------------------------------------------------- /packages/timeline/src/components/icons/SvgIcon.tsx: -------------------------------------------------------------------------------- 1 | import { GroupProps } from "@react-three/fiber"; 2 | 3 | import { useSvgShapes } from "./useSvgShapes"; 4 | import { SvgShapeMesh } from "./SvgShapeMesh"; 5 | import { IconType } from "./types"; 6 | import { icons } from "./icons"; 7 | 8 | export function SvgIcon({ 9 | icon = "misc", 10 | groupProps = {} 11 | }: { 12 | icon?: IconType 13 | groupProps?: GroupProps 14 | }) { 15 | const iconUrl = icons[icon] 16 | const shapes = useSvgShapes(iconUrl) 17 | 18 | return ( 19 | 20 | {shapes.map(item => 21 | 22 | )} 23 | 24 | ); 25 | } -------------------------------------------------------------------------------- /packages/timeline/src/components/icons/loadSvgShapes.ts: -------------------------------------------------------------------------------- 1 | import { SVGLoader } from "three/examples/jsm/Addons.js" 2 | 3 | import { SvgShape } from "./types"; 4 | 5 | // with which the threejs engine will make shapes 6 | export async function loadSvgShapes(url: string): Promise { 7 | return new Promise(resolve => 8 | new SVGLoader().load(url, shapes => ( 9 | resolve(shapes.paths.map((group, index) => ( 10 | group.toShapes(true).map(shape => ({ 11 | shape, 12 | color: group.userData?.style.fill || '#ffffff', 13 | index 14 | })) 15 | )).flat()) 16 | )) 17 | ) 18 | } -------------------------------------------------------------------------------- /packages/timeline/src/components/icons/types.ts: -------------------------------------------------------------------------------- 1 | import { Shape } from "three"; 2 | 3 | import { icons } from "./icons"; 4 | 5 | export type SvgShape = { 6 | shape: Shape; 7 | color: any; 8 | index: number; 9 | } 10 | export type IconType = keyof typeof icons -------------------------------------------------------------------------------- /packages/timeline/src/components/icons/useSvgShapes.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | import { loadSvgShapes } from "./loadSvgShapes"; 4 | import { SvgShape } from "./types"; 5 | 6 | export function useSvgShapes(url: string): SvgShape[] { 7 | const [shapes, set] = useState([]); 8 | useEffect(() => { 9 | loadSvgShapes(url).then(set) 10 | }, [url]); 11 | return shapes 12 | } -------------------------------------------------------------------------------- /packages/timeline/src/components/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { 3 | Cell, 4 | ImageCell, 5 | VideoCell, 6 | TextCell, 7 | type SpecializedCellProps 8 | } from "./cells" 9 | 10 | export { TimelineControls } from "./controls" 11 | export { HorizontalScroller, VerticalScroller } from "./scroller" 12 | export { Timeline, TopBarTimeScale, Cells, Grid, type JumpAt } from "./timeline" -------------------------------------------------------------------------------- /packages/timeline/src/components/scroller/index.ts: -------------------------------------------------------------------------------- 1 | export { HorizontalScroller } from "./HorizontalScroller" 2 | export { VerticalScroller } from "./VerticalScroller" -------------------------------------------------------------------------------- /packages/timeline/src/components/slider/VerticalSlider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/utils/cn" 4 | 5 | import { TimelineSliderBase } from "./TimelineSliderBase" 6 | 7 | const VerticalSlider = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, ...props }, ref) => ( 11 | 20 | )) 21 | 22 | VerticalSlider.displayName = "VerticalSlider" 23 | 24 | export { VerticalSlider } 25 | -------------------------------------------------------------------------------- /packages/timeline/src/components/slider/index.ts: -------------------------------------------------------------------------------- 1 | export { HorizontalSlider } from "./HorizontalSlider" 2 | export { TimelineSliderBase } from "./TimelineSliderBase" 3 | export { VerticalSlider } from "./VerticalSlider" -------------------------------------------------------------------------------- /packages/timeline/src/components/timeline/index.ts: -------------------------------------------------------------------------------- 1 | export { Cursor } from "./Cursor" 2 | export { Timeline } from "./Timeline" 3 | export { TopBarTimeScale } from "./TopBarTimeScale" 4 | export { LeftBarTrackScale } from "./LeftBarTrackScale" 5 | export { Cells } from "./Cells" 6 | export { Grid } from "./Grid" 7 | export { type JumpAt } from "./types" -------------------------------------------------------------------------------- /packages/timeline/src/components/timeline/types.ts: -------------------------------------------------------------------------------- 1 | import { Group, Object3DEventMap } from "three" 2 | 3 | export type TimelineCursorImpl = Group 4 | 5 | // application-provided callback used to seek into a specific timestamp 6 | export type JumpAt = (jumpAtInMs: number) => void 7 | 8 | // application-provided callback used to determine if we are playing back a stream or not 9 | export type IsPlaying = () => boolean 10 | 11 | // application-provided callback used to toggle the playback 12 | export type TogglePlayback = (forcePlaying?: boolean) => { 13 | wasPlaying: boolean 14 | isPlaying: boolean 15 | } 16 | -------------------------------------------------------------------------------- /packages/timeline/src/compute/README.md: -------------------------------------------------------------------------------- 1 | The purpose of those functions is to compute some metrics without even touching to the Zustand state in read or write 2 | 3 | -------------------------------------------------------------------------------- /packages/timeline/src/constants/defaults.ts: -------------------------------------------------------------------------------- 1 | import { ClapTimelineTheme } from "@/types" 2 | import { pastel } from "./themes" 3 | 4 | 5 | export const DEFAULT_MIN_ZOOM = 0.2 6 | export const DEFAULT_MAX_ZOOM = 7 7 | export const DEFAULT_ZOOM_SPEED = 1.7 // high = faster 8 | export const DEFAULT_ZOOM_DAMPING_FACTOR = 0.3 // low : delay, high : faster/snappier 9 | export const DEFAULT_SHOW_FPS = false 10 | export const DEFAULT_FRAMELOOP = "demand" 11 | 12 | export const DEFAULT_THEMES: Record = { 13 | pastel, 14 | } -------------------------------------------------------------------------------- /packages/timeline/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | DEFAULT_MIN_ZOOM, 3 | DEFAULT_MAX_ZOOM, 4 | DEFAULT_ZOOM_SPEED, 5 | DEFAULT_ZOOM_DAMPING_FACTOR, 6 | DEFAULT_SHOW_FPS, 7 | DEFAULT_FRAMELOOP, 8 | DEFAULT_THEMES, 9 | } from "./defaults" 10 | 11 | export { 12 | DEFAULT_DURATION_IN_MS_PER_STEP, 13 | DEFAULT_NB_TRACKS, 14 | DEFAULT_COLUMNS_PER_SLICE, 15 | PROMPT_STEP_HEIGHT_IN_PX, 16 | PREVIEW_STEP_HEIGHT_IN_PX, 17 | NB_MAX_SHOTS 18 | } from "./grid" 19 | 20 | export { 21 | segmentVisibilityPriority 22 | } from "./priorities" 23 | 24 | export { 25 | pastel, 26 | leftBarTrackScaleWidth 27 | } from "./themes" 28 | -------------------------------------------------------------------------------- /packages/timeline/src/constants/priorities.ts: -------------------------------------------------------------------------------- 1 | import { SegmentVisibility } from "@/types" 2 | 3 | // used for sort 4 | export const segmentVisibilityPriority: Record = { 5 | // the segment is visible, and the user explicitly requested to render it before the others 6 | [SegmentVisibility.DEMANDED]: 3, 7 | 8 | // TODO: add some implicit intermediary priority options 9 | // such as SELECTED, HOVERED.. 10 | 11 | // the segment (or at least a portion of it) is currently visible in the sliding window 12 | [SegmentVisibility.VISIBLE]: 2, 13 | 14 | // the segment is hidden, but not too far from the sliding window 15 | [SegmentVisibility.BUFFERED]: 1, 16 | 17 | // fully hidden, far from the sliding window 18 | [SegmentVisibility.HIDDEN]: 0 19 | } 20 | -------------------------------------------------------------------------------- /packages/timeline/src/demo.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | #root { 6 | width: 100vw; 7 | height: 100svh; 8 | } -------------------------------------------------------------------------------- /packages/timeline/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useAnimationFrame } from "./useAnimationFrame" 2 | export { useAxis } from "./useAxis" 3 | export { useVerticalGridLines } from "./useVerticalGridLines" 4 | export { useHorizontalGridLines } from "./useHorizontalGridLines" 5 | export { useTimeline } from "./useTimeline" 6 | export { useSegmentChanges } from "./useSegmentChanges" 7 | export { useSegmentLoader } from "./useSegmentLoader" -------------------------------------------------------------------------------- /packages/timeline/src/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | export function useDebounce(value: T, delay?: number): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value) 5 | 6 | useEffect(() => { 7 | const timer = setTimeout(() => setDebouncedValue(value), delay || 500) 8 | 9 | return () => { 10 | clearTimeout(timer) 11 | } 12 | }, [value, delay]) 13 | 14 | return debouncedValue 15 | } -------------------------------------------------------------------------------- /packages/timeline/src/hooks/useHoveredSegment.ts: -------------------------------------------------------------------------------- 1 | import { useTimeline } from "./useTimeline" 2 | 3 | export function useHoveredSegment(segmentId: string): boolean { 4 | const hoveredSegment = useTimeline(s => s.hoveredSegment) 5 | /* deprecateed 6 | 7 | 8 | useEffect(() => { 9 | const cursor = hoveredSegment ? 'pointer' : 'auto' 10 | if (document.body.style.cursor !== cursor) { 11 | document.body.style.cursor = cursor 12 | } 13 | return () => { document.body.style.cursor = 'auto' } 14 | }, [hoveredSegment]) 15 | 16 | */ 17 | if (hoveredSegment?.id === segmentId) { 18 | return true 19 | } else { 20 | return false 21 | } 22 | } -------------------------------------------------------------------------------- /packages/timeline/src/hooks/useSegment.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | 3 | import { useTimeline } from "./useTimeline" 4 | import { TimelineSegment } from "@/types" 5 | 6 | /** 7 | * Can be used to subscribe to a segment 8 | * @param segment 9 | * @returns 10 | */ 11 | export function useSegment(segmentId: string): TimelineSegment | undefined { 12 | const [segment, setSegment] = useState() 13 | const atLeastOneSegmentChanged = useTimeline(s => s.atLeastOneSegmentChanged) 14 | 15 | useEffect(() => { 16 | // TODO: not implemented yet 17 | }, [atLeastOneSegmentChanged]) 18 | 19 | return segment 20 | } -------------------------------------------------------------------------------- /packages/timeline/src/types/grid.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "three" 2 | 3 | export type RenderedCell = { 4 | position: Vector3 5 | width: number 6 | height: number 7 | color: string 8 | } -------------------------------------------------------------------------------- /packages/timeline/src/types/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export type { 3 | RenderedCell 4 | } from "./grid" 5 | 6 | export { 7 | SegmentVisibility, 8 | SegmentEditionStatus, 9 | } from "./timeline" 10 | 11 | export type { 12 | BrowserOnlySegmentData, 13 | TimelineSegment, 14 | ContentSizeMetrics, 15 | TimelineStore, 16 | TimelineStoreState, 17 | TimelineStoreModifiers 18 | } from "./timeline" 19 | 20 | export type { 21 | ClapTimelineTheme, 22 | ClapSegmentCategoryHSL, 23 | ClapSegmentCategoryColors, 24 | ClapSegmentColorScheme 25 | } from "./theme" 26 | 27 | export { 28 | RenderingStrategy 29 | } from "./rendering" 30 | 31 | export type { 32 | SegmentResolver 33 | } from "./rendering" -------------------------------------------------------------------------------- /packages/timeline/src/utils/clamp.ts: -------------------------------------------------------------------------------- 1 | export function clamp(value: number, min: number, max: number) { 2 | return Math.max(min, Math.min(max, value)) 3 | } -------------------------------------------------------------------------------- /packages/timeline/src/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /packages/timeline/src/utils/getAudioBuffer.ts: -------------------------------------------------------------------------------- 1 | import { readFileAsArrayBuffer } from "./readFileAsArrayBuffer" 2 | 3 | export async function getAudioBuffer(input: File | string): Promise { 4 | const audioContext = new AudioContext() // initialize AudioContext 5 | const arrayBuffer = await readFileAsArrayBuffer(input) 6 | 7 | // decode audio data from your arrayBuffer 8 | return new Promise((resolve, reject) => { 9 | audioContext.decodeAudioData(arrayBuffer, (buffer) => { 10 | resolve(buffer) 11 | }, (err) => { 12 | reject(err) 13 | }) 14 | }) 15 | } -------------------------------------------------------------------------------- /packages/timeline/src/utils/setBodyCursor.ts: -------------------------------------------------------------------------------- 1 | 2 | export type CursorName = 'auto' | 'grab' | 'ew-resize' | 'pointer' 3 | 4 | export function setBodyCursor(cursor: CursorName) { 5 | if (cursor !== document.body.style.cursor) { 6 | document.body.style.cursor = cursor 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/timeline/src/utils/similar.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "three" 2 | 3 | export function similar(v1: Vector3, v2: Vector3, epsilon = Number.EPSILON) { 4 | 5 | return ( ( Math.abs( v1.x - v2.x) < epsilon ) && ( Math.abs( v1.y - v2.y ) < epsilon ) && ( Math.abs( v1.z - v2.z ) < epsilon) ); 6 | } -------------------------------------------------------------------------------- /packages/timeline/src/utils/sleep.ts: -------------------------------------------------------------------------------- 1 | export const sleep = async (durationInMs: number) => 2 | new Promise((resolve) => { 3 | setTimeout(() => { 4 | resolve(true) 5 | }, durationInMs) 6 | }) -------------------------------------------------------------------------------- /packages/timeline/src/utils/sliceSegments.ts: -------------------------------------------------------------------------------- 1 | import { ClapSegmentCategory, ClapSegmentFilteringMode, filterSegmentsWithinRange } from "@aitube/clap" 2 | 3 | import { TimelineSegment } from "@/types" 4 | 5 | // TODO put this in a web workers for smoother operations? 6 | export function sliceSegments({ 11 | segments, 12 | afterTimeInMs, 13 | beforeTimeInMs, 14 | }: { 15 | segments: T[] 16 | afterTimeInMs: number 17 | beforeTimeInMs: number 18 | }): T[] { 19 | const result = filterSegmentsWithinRange( 20 | ClapSegmentFilteringMode.ANY, 21 | afterTimeInMs, 22 | beforeTimeInMs, 23 | segments 24 | ) 25 | return result 26 | } -------------------------------------------------------------------------------- /packages/timeline/src/utils/throttle.ts: -------------------------------------------------------------------------------- 1 | export const throttle = ( 2 | fn: (...args: A) => R, 3 | delay: number 4 | ): [(...args: A) => R | undefined, () => void] => { 5 | let wait = false; 6 | let timeout: undefined | number; 7 | let cancelled = false; 8 | 9 | return [ 10 | (...args: A) => { 11 | if (cancelled) return undefined; 12 | if (wait) return undefined; 13 | 14 | const val = fn(...args); 15 | 16 | wait = true; 17 | 18 | timeout = window.setTimeout(() => { 19 | wait = false; 20 | }, delay); 21 | 22 | return val; 23 | }, 24 | () => { 25 | cancelled = true; 26 | clearTimeout(timeout); 27 | }, 28 | ]; 29 | }; -------------------------------------------------------------------------------- /packages/timeline/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("tailwindcss").Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | "./src/components/**/*.{ts,tsx}", 6 | "./src/**/*.{ts,tsx}", 7 | ], 8 | theme: { 9 | container: { 10 | center: true, 11 | padding: "2rem", 12 | screens: { 13 | "2xl": "1400px", 14 | }, 15 | }, 16 | extend: { 17 | fontSize: { 18 | "5xs": "8px", 19 | "4xs": "9px", 20 | "3xs": "10px", 21 | "2xs": "11px" 22 | }, 23 | containers: { 24 | "6xs": "2rem", 25 | "5xs": "4rem", 26 | "4xs": "8rem", 27 | "3xs": "12rem", 28 | "2xs": "16rem", 29 | } 30 | }, 31 | }, 32 | plugins: [] 33 | } -------------------------------------------------------------------------------- /packages/timeline/test/test.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbilcke-hf/clapper/7655f51ff59b6cc6bd79b6e43c832a89c2fc1632/packages/timeline/test/test.tsx -------------------------------------------------------------------------------- /packages/timeline/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/timeline/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | }, 10 | "include": [ 11 | "src/**/*.ts", 12 | "src/**/*.tsx" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/timeline/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import path from 'path' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | // resolve monorepo packages 9 | resolve: { 10 | alias: { 11 | "@aitube/clap": path.resolve(__dirname, "../clap/dist"), 12 | '@': path.resolve(__dirname, 'src'), 13 | }, 14 | }, 15 | server: { 16 | fs: { 17 | // Allow serving files from one level up to the project root 18 | allow: ['..'], 19 | }, 20 | }, 21 | optimizeDeps: { 22 | exclude: ['@aitube/clap'], // Exclude the clap package from pre-bundling 23 | }, 24 | }) --------------------------------------------------------------------------------