diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 443368619..196820aee 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -510,9 +510,11 @@ async function scaffoldProject( ): Promise { mkdirSync(destDir, { recursive: true }); - // Use bundled template if available, otherwise fetch from GitHub + // Use bundled template if available, otherwise fetch from GitHub. + // Check for index.html inside the dir — an empty directory left by the + // build toolchain should not prevent the remote fetch fallback. const templateDir = getStaticTemplateDir(templateId); - if (existsSync(templateDir)) { + if (existsSync(join(templateDir, "index.html"))) { cpSync(templateDir, destDir, { recursive: true }); } else { await fetchRemoteTemplate(templateId, destDir); diff --git a/packages/core/package.json b/packages/core/package.json index 5c5339647..e7b4f59c5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -42,6 +42,14 @@ "import": "./src/studio-api/helpers/screenshotClip.ts", "types": "./src/studio-api/helpers/screenshotClip.ts" }, + "./studio-api/manual-edits-render-script": { + "import": "./src/studio-api/helpers/manualEditsRenderScript.ts", + "types": "./src/studio-api/helpers/manualEditsRenderScript.ts" + }, + "./studio-api/studio-motion-render-script": { + "import": "./src/studio-api/helpers/studioMotionRenderScript.ts", + "types": "./src/studio-api/helpers/studioMotionRenderScript.ts" + }, "./text": { "import": "./src/text/index.ts", "types": "./src/text/index.ts" @@ -81,6 +89,14 @@ "import": "./dist/studio-api/helpers/screenshotClip.js", "types": "./dist/studio-api/helpers/screenshotClip.d.ts" }, + "./studio-api/manual-edits-render-script": { + "import": "./dist/studio-api/helpers/manualEditsRenderScript.js", + "types": "./dist/studio-api/helpers/manualEditsRenderScript.d.ts" + }, + "./studio-api/studio-motion-render-script": { + "import": "./dist/studio-api/helpers/studioMotionRenderScript.js", + "types": "./dist/studio-api/helpers/studioMotionRenderScript.d.ts" + }, "./text": { "import": "./dist/text/index.js", "types": "./dist/text/index.d.ts" diff --git a/packages/engine/src/services/streamingEncoder.ts b/packages/engine/src/services/streamingEncoder.ts index 03c0d0735..740a8afd2 100644 --- a/packages/engine/src/services/streamingEncoder.ts +++ b/packages/engine/src/services/streamingEncoder.ts @@ -389,6 +389,9 @@ export async function spawnStreamingEncoder( exitPromiseResolve?.(); }); + ffmpeg.stdin?.on("error", () => {}); + ffmpeg.stdout?.on("error", () => {}); + // Handle abort signal const onAbort = () => { if (exitStatus === "running") { diff --git a/packages/studio/src/App.tsx b/packages/studio/src/App.tsx index 5f734be5e..e11c55230 100644 --- a/packages/studio/src/App.tsx +++ b/packages/studio/src/App.tsx @@ -4106,6 +4106,7 @@ export function StudioApp() { projectId={projectId} assets={assets} element={domEditGroupSelections.length > 1 ? null : domEditSelection} + multiSelectCount={domEditGroupSelections.length} copiedAgentPrompt={copiedAgentPrompt} onClearSelection={clearDomSelection} onSetStyle={handleDomStyleCommit} @@ -4135,9 +4136,9 @@ export function StudioApp() { projectId={projectId} onDelete={renderQueue.deleteRender} onClearCompleted={renderQueue.clearCompleted} - onStartRender={async (format, quality, resolution) => { + onStartRender={async (format, quality, resolution, fps) => { await waitForPendingDomEditSaves(); - await renderQueue.startRender({ fps: 30, quality, format, resolution }); + await renderQueue.startRender({ fps, quality, format, resolution }); }} isRendering={renderQueue.isRendering} /> diff --git a/packages/studio/src/components/editor/PropertyPanel.tsx b/packages/studio/src/components/editor/PropertyPanel.tsx index d46468651..4f06bbf42 100644 --- a/packages/studio/src/components/editor/PropertyPanel.tsx +++ b/packages/studio/src/components/editor/PropertyPanel.tsx @@ -54,6 +54,7 @@ interface PropertyPanelProps { projectId: string; assets: string[]; element: DomEditSelection | null; + multiSelectCount?: number; copiedAgentPrompt: boolean; onClearSelection: () => void; onSetStyle: (prop: string, value: string) => void | Promise; @@ -2212,6 +2213,7 @@ export const PropertyPanel = memo(function PropertyPanel({ projectId, assets, element, + multiSelectCount = 0, copiedAgentPrompt, onClearSelection, onSetStyle, @@ -2258,12 +2260,29 @@ export const PropertyPanel = memo(function PropertyPanel({ if (!element) { return (
- -

Select an element in the preview.

-

- The inspector is tuned for element edits with safer geometry controls, color picking, and - cleaner grouped layer controls. -

+ {multiSelectCount > 1 ? ( + <> + +

+ {multiSelectCount} elements selected +

+

+ Select a single element to edit its properties. Click an element in the preview or use + the timeline layer panel. +

+ + ) : ( + <> + +

+ Select an element in the preview. +

+

+ The inspector is tuned for element edits with safer geometry controls, color picking, + and cleaner grouped layer controls. +

+ + )}
); } diff --git a/packages/studio/src/components/editor/manualEditingAvailability.ts b/packages/studio/src/components/editor/manualEditingAvailability.ts index 057a47a7b..2a5c7cb74 100644 --- a/packages/studio/src/components/editor/manualEditingAvailability.ts +++ b/packages/studio/src/components/editor/manualEditingAvailability.ts @@ -32,7 +32,7 @@ const env = import.meta.env as StudioFeatureFlagEnv; export const STUDIO_PREVIEW_MANUAL_EDITING_ENABLED = resolveStudioBooleanEnvFlag( env, [STUDIO_PREVIEW_MANUAL_DRAGGING_ENV, "VITE_STUDIO_PREVIEW_MANUAL_EDITING_ENABLED"], - false, + true, ); export const STUDIO_INSPECTOR_PANELS_ENABLED = resolveStudioBooleanEnvFlag( @@ -44,7 +44,7 @@ export const STUDIO_INSPECTOR_PANELS_ENABLED = resolveStudioBooleanEnvFlag( export const STUDIO_MOTION_PANEL_ENABLED = resolveStudioBooleanEnvFlag( env, [STUDIO_MOTION_PANEL_ENV, "VITE_STUDIO_MOTION_PANEL_ENABLED"], - false, + true, ); export const STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED = diff --git a/packages/studio/src/components/renders/RenderQueue.tsx b/packages/studio/src/components/renders/RenderQueue.tsx index 297a9bbfe..e0a51dc8a 100644 --- a/packages/studio/src/components/renders/RenderQueue.tsx +++ b/packages/studio/src/components/renders/RenderQueue.tsx @@ -6,6 +6,7 @@ type StartRenderHandler = ( format: "mp4" | "webm" | "mov", quality: "draft" | "standard" | "high", resolution: ResolutionPreset | "auto", + fps: 24 | 30 | 60, ) => void | Promise; interface RenderQueueProps { @@ -133,6 +134,7 @@ function FormatExportButton({ const [format, setFormat] = useState<"mp4" | "webm" | "mov">("mp4"); const [quality, setQuality] = useState<"draft" | "standard" | "high">("standard"); const [resolution, setResolution] = useState("auto"); + const [fps, setFps] = useState<24 | 30 | 60>(30); // MOV (ProRes) is a fixed-quality codec — quality selector has no effect. const showQuality = format !== "mov"; @@ -172,6 +174,17 @@ function FormatExportButton({ ))} )} +