mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
codebase overhaul (#697)
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import type { EditorCore } from "@/core";
|
||||
import type { RootNode } from "@/services/renderer/nodes/root-node";
|
||||
import type { ExportOptions, ExportResult } from "@/types/export";
|
||||
import { SceneExporter } from "@/services/renderer/scene-exporter";
|
||||
import { buildScene } from "@/services/renderer/scene-builder";
|
||||
import { createTimelineAudioBuffer } from "@/lib/media/audio";
|
||||
|
||||
export class RendererManager {
|
||||
private renderTree: RootNode | null = null;
|
||||
private listeners = new Set<() => void>();
|
||||
|
||||
constructor(private editor: EditorCore) {}
|
||||
|
||||
setRenderTree({ renderTree }: { renderTree: RootNode | null }): void {
|
||||
this.renderTree = renderTree;
|
||||
this.notify();
|
||||
}
|
||||
|
||||
getRenderTree(): RootNode | null {
|
||||
return this.renderTree;
|
||||
}
|
||||
|
||||
async exportProject({
|
||||
options,
|
||||
}: {
|
||||
options: ExportOptions;
|
||||
}): Promise<ExportResult> {
|
||||
const { format, quality, fps, includeAudio, onProgress, onCancel } =
|
||||
options;
|
||||
|
||||
try {
|
||||
const tracks = this.editor.timeline.getTracks();
|
||||
const mediaAssets = this.editor.media.getAssets();
|
||||
const activeProject = this.editor.project.getActive();
|
||||
|
||||
if (!activeProject) {
|
||||
return { success: false, error: "No active project" };
|
||||
}
|
||||
|
||||
const duration = this.editor.timeline.getTotalDuration();
|
||||
if (duration === 0) {
|
||||
return { success: false, error: "Project is empty" };
|
||||
}
|
||||
|
||||
const exportFps = fps || activeProject.settings.fps;
|
||||
const canvasSize = activeProject.settings.canvasSize;
|
||||
|
||||
let audioBuffer: AudioBuffer | null = null;
|
||||
if (includeAudio) {
|
||||
onProgress?.({ progress: 0.05 });
|
||||
audioBuffer = await createTimelineAudioBuffer({
|
||||
tracks,
|
||||
mediaAssets,
|
||||
duration,
|
||||
});
|
||||
}
|
||||
|
||||
const scene = buildScene({
|
||||
tracks,
|
||||
mediaAssets,
|
||||
duration,
|
||||
canvasSize,
|
||||
background: activeProject.settings.background,
|
||||
});
|
||||
|
||||
const exporter = new SceneExporter({
|
||||
width: canvasSize.width,
|
||||
height: canvasSize.height,
|
||||
fps: exportFps,
|
||||
format,
|
||||
quality,
|
||||
shouldIncludeAudio: !!includeAudio,
|
||||
audioBuffer: audioBuffer || undefined,
|
||||
});
|
||||
|
||||
exporter.on("progress", (progress) => {
|
||||
const adjustedProgress = includeAudio
|
||||
? 0.05 + progress * 0.95
|
||||
: progress;
|
||||
onProgress?.({ progress: adjustedProgress });
|
||||
});
|
||||
|
||||
let cancelled = false;
|
||||
const checkCancel = () => {
|
||||
if (onCancel?.()) {
|
||||
cancelled = true;
|
||||
exporter.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
const cancelInterval = setInterval(checkCancel, 100);
|
||||
|
||||
try {
|
||||
const buffer = await exporter.export({ rootNode: scene });
|
||||
clearInterval(cancelInterval);
|
||||
|
||||
if (cancelled) {
|
||||
return { success: false, cancelled: true };
|
||||
}
|
||||
|
||||
if (!buffer) {
|
||||
return { success: false, error: "Export failed to produce buffer" };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
buffer,
|
||||
};
|
||||
} finally {
|
||||
clearInterval(cancelInterval);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Export failed:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown export error",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
subscribe(listener: () => void): () => void {
|
||||
this.listeners.add(listener);
|
||||
return () => this.listeners.delete(listener);
|
||||
}
|
||||
|
||||
private notify(): void {
|
||||
this.listeners.forEach((fn) => fn());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user