diff options
| author | cashmere <cashmere@autistici.org> | 2026-07-30 18:17:45 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-07-30 18:18:15 +0200 |
| commit | ed02ba872c02eafed45f406c68d389d0a1112a4e (patch) | |
| tree | 40530d77cfe306621915f067c66531cb9e783a4d /apps/web/src/core | |
| parent | e837e7504379e100ff3b56282577ec67bb88bec9 (diff) | |
| download | opencut-ed02ba872c02eafed45f406c68d389d0a1112a4e.tar.gz | |
feat: Electron desktop app, native ffmpeg export, fully static web
- apps/desktop: Electron shell serving the static web build over a
privileged opencut:// protocol; exports stream frames over IPC to a
bundled ffmpeg (libx264/libvpx-vp9, BT.709 tagged) with backpressure.
electron-builder packaging: AppImage, deb, rpm.
- apps/web: dead backend deleted (better-auth, drizzle/pg, upstash,
botid, freesound proxy, blog, rss, env validation); next build emits
a zero-env static export; /editor/[project_id] -> /editor?project=.
- Native exporter mirrors SceneExporter; activates when the preload
bridge is present; web keeps the mediabunny path.
- Docker self-hosting now builds and serves the static export via nginx.
- Release workflow: v* tags publish static web tarball + Linux packages.
- Changelog 0.4.0; README/AGENTS/CONTRIBUTING/roadmap updated.
- GPUI/Rust rewrite parked on branch rust-rewrite.
Diffstat (limited to 'apps/web/src/core')
| -rw-r--r-- | apps/web/src/core/managers/renderer-manager.ts | 42 |
1 files changed, 37 insertions, 5 deletions
diff --git a/apps/web/src/core/managers/renderer-manager.ts b/apps/web/src/core/managers/renderer-manager.ts index d73e3e58..29324eed 100644 --- a/apps/web/src/core/managers/renderer-manager.ts +++ b/apps/web/src/core/managers/renderer-manager.ts @@ -3,6 +3,8 @@ import type { RootNode } from "@/services/renderer/nodes/root-node"; import type { ExportOptions, ExportResult } from "@/export"; import { CanvasRenderer } from "@/services/renderer/canvas-renderer"; import { SceneExporter } from "@/services/renderer/scene-exporter"; +import { NativeExporter } from "@/services/renderer/native-exporter"; +import { getDesktopBridge } from "@/desktop/bridge"; import { buildScene } from "@/services/renderer/scene-builder"; import { createTimelineAudioBuffer } from "@/media/audio"; import { formatTimecode } from "opencut-wasm"; @@ -214,7 +216,7 @@ export class RendererManager { background: activeProject.settings.background, }); - const exporter = new SceneExporter({ + const exportParams = { width: exportSize.width, height: exportSize.height, fps: exportFps, @@ -222,7 +224,33 @@ export class RendererManager { quality, shouldIncludeAudio: !!includeAudio, audioBuffer: audioBuffer || undefined, - }); + }; + + type ProjectExporter = { + on( + event: "progress", + listener: (progress: number) => void, + ): unknown; + cancel(): void; + export({ + rootNode, + }: { + rootNode: RootNode; + }): Promise<ArrayBuffer | string | null>; + }; + + let exporter: ProjectExporter; + if (getDesktopBridge()) { + const safeName = + activeProject.metadata.name.replace(/[<>:"/\\|?*]/g, "-").trim() || + "export"; + exporter = new NativeExporter({ + ...exportParams, + fileName: `${safeName}.${format}`, + }); + } else { + exporter = new SceneExporter(exportParams); + } exporter.on("progress", (progress) => { const adjustedProgress = includeAudio @@ -242,20 +270,24 @@ export class RendererManager { const cancelInterval = setInterval(checkCancel, 100); try { - const buffer = await exporter.export({ rootNode: scene }); + const result = await exporter.export({ rootNode: scene }); clearInterval(cancelInterval); if (cancelled) { return { success: false, cancelled: true }; } - if (!buffer) { + if (typeof result === "string") { + return { success: true, filePath: result }; + } + + if (!result) { return { success: false, error: "Export failed to produce buffer" }; } return { success: true, - buffer, + buffer: result, }; } finally { clearInterval(cancelInterval); |
