refactor(editor): extract smoke export config

This commit is contained in:
wiiiii123
2026-05-20 21:55:44 +07:00
parent 4f729b7eee
commit 8044efd8a5
3 changed files with 252 additions and 130 deletions
+1 -130
View File
@@ -62,14 +62,12 @@ import {
type ExportPipelineModel,
type ExportProgress,
type ExportQuality,
type ExportRenderBackend,
type ExportSettings,
FrameRenderer,
GIF_SIZE_PRESETS,
GifExporter,
type GifFrameRate,
type GifSizePreset,
isValidMp4FrameRate,
ModernVideoExporter,
probeSupportedMp4Dimensions,
type SupportedMp4Dimensions,
@@ -140,6 +138,7 @@ import {
validateProjectData,
} from "./projectPersistence";
import { SettingsPanel } from "./SettingsPanel";
import { getDevOpenRecordingConfig, getSmokeExportConfig } from "./smokeExportConfig";
import { useVideoEditorAudio } from "./audio/useVideoEditorAudio";
import {
APP_HEADER_ICON_BUTTON_CLASS,
@@ -233,27 +232,6 @@ type CancelableExporter = {
cancel(): void;
};
type SmokeExportConfig = {
enabled: boolean;
inputPath: string | null;
outputPath: string | null;
useNativeExport: boolean;
encodingMode?: ExportEncodingMode;
shadowIntensity?: number;
webcamInputPath?: string | null;
webcamShadow?: number;
webcamSize?: number;
pipelineModel?: ExportPipelineModel;
backendPreference?: ExportBackendPreference;
renderBackend?: ExportRenderBackend;
maxEncodeQueue?: number;
maxDecodeQueue?: number;
maxPendingFrames?: number;
projectPath?: string | null;
quality?: ExportQuality;
fps?: ExportMp4FrameRate;
};
const EXPORT_BLOB_STREAM_CHUNK_BYTES = 16 * 1024 * 1024;
async function streamExportBlobToTempFile(blob: Blob, extension: string): Promise<string | null> {
@@ -312,11 +290,6 @@ type SaveProjectOptions = {
captureThumbnail?: boolean;
};
type DevOpenRecordingConfig = {
inputPath: string | null;
webcamInputPath: string | null;
};
async function writeSmokeExportReport(
outputPath: string | null,
report: Record<string, unknown>,
@@ -366,108 +339,6 @@ function cloneStructured<T>(value: T): T {
return globalThis.structuredClone(value);
}
function parseSmokeExportNumber(value: string | null): number | undefined {
if (value === null) {
return undefined;
}
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
}
function parseSmokeExportNonNegativeNumber(value: string | null): number | undefined {
if (value === null) {
return undefined;
}
const parsed = Number.parseFloat(value);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
}
function parseSmokeExportQuality(value: string | null): ExportQuality | undefined {
if (value === "medium" || value === "good" || value === "high" || value === "source") {
return value;
}
return undefined;
}
function parseSmokeExportFps(value: string | null): ExportMp4FrameRate | undefined {
if (value === null) return undefined;
const parsed = Number.parseInt(value, 10);
return isValidMp4FrameRate(parsed) ? parsed : undefined;
}
function parseSmokeRenderBackend(value: string | null): ExportRenderBackend | undefined {
return value === "webgl" || value === "webgpu" ? value : undefined;
}
function getSmokeExportConfig(search: string): SmokeExportConfig {
const params = new URLSearchParams(search);
const enabled = params.get("smokeExport") === "1";
return {
enabled,
inputPath: enabled ? params.get("smokeInput") : null,
outputPath: enabled ? params.get("smokeOutput") : null,
useNativeExport: enabled ? params.get("smokeUseNativeExport") === "1" : false,
encodingMode:
enabled && params.get("smokeEncodingMode") === "fast"
? "fast"
: enabled && params.get("smokeEncodingMode") === "balanced"
? "balanced"
: enabled && params.get("smokeEncodingMode") === "quality"
? "quality"
: undefined,
shadowIntensity: enabled
? parseSmokeExportNonNegativeNumber(params.get("smokeShadowIntensity"))
: undefined,
webcamInputPath: enabled ? params.get("smokeWebcamInput") : null,
webcamShadow: enabled
? parseSmokeExportNonNegativeNumber(params.get("smokeWebcamShadow"))
: undefined,
webcamSize: enabled
? parseSmokeExportNonNegativeNumber(params.get("smokeWebcamSize"))
: undefined,
pipelineModel:
enabled && params.get("smokePipelineModel") === "modern"
? "modern"
: enabled && params.get("smokePipelineModel") === "legacy"
? "legacy"
: undefined,
backendPreference:
enabled && params.get("smokeBackendPreference") === "auto"
? "auto"
: enabled && params.get("smokeBackendPreference") === "webcodecs"
? "webcodecs"
: enabled && params.get("smokeBackendPreference") === "breeze"
? "breeze"
: undefined,
renderBackend: enabled
? parseSmokeRenderBackend(params.get("smokeRenderBackend"))
: undefined,
maxEncodeQueue: enabled
? parseSmokeExportNumber(params.get("smokeMaxEncodeQueue"))
: undefined,
maxDecodeQueue: enabled
? parseSmokeExportNumber(params.get("smokeMaxDecodeQueue"))
: undefined,
maxPendingFrames: enabled
? parseSmokeExportNumber(params.get("smokeMaxPendingFrames"))
: undefined,
projectPath: enabled ? params.get("smokeProject") : null,
quality: enabled ? parseSmokeExportQuality(params.get("smokeQuality")) : undefined,
fps: enabled ? parseSmokeExportFps(params.get("smokeFps")) : undefined,
};
}
function getDevOpenRecordingConfig(search: string): DevOpenRecordingConfig {
const params = new URLSearchParams(search);
return {
inputPath: params.get("devOpenInput"),
webcamInputPath: params.get("devOpenWebcam"),
};
}
function isComparableObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
@@ -0,0 +1,120 @@
import { describe, expect, it } from "vitest";
import { getDevOpenRecordingConfig, getSmokeExportConfig } from "./smokeExportConfig";
describe("getSmokeExportConfig", () => {
it("keeps smoke export disabled when the flag is absent", () => {
expect(getSmokeExportConfig("?smokeInput=/tmp/input.mp4")).toEqual({
enabled: false,
inputPath: null,
outputPath: null,
useNativeExport: false,
encodingMode: undefined,
shadowIntensity: undefined,
webcamInputPath: null,
webcamShadow: undefined,
webcamSize: undefined,
pipelineModel: undefined,
backendPreference: undefined,
renderBackend: undefined,
maxEncodeQueue: undefined,
maxDecodeQueue: undefined,
maxPendingFrames: undefined,
projectPath: null,
quality: undefined,
fps: undefined,
});
});
it("parses the enabled smoke export query contract", () => {
const config = getSmokeExportConfig(
"?smokeExport=1" +
"&smokeInput=/tmp/input.mp4" +
"&smokeOutput=/tmp/output.mp4" +
"&smokeUseNativeExport=1" +
"&smokeEncodingMode=quality" +
"&smokeShadowIntensity=0" +
"&smokeWebcamInput=/tmp/webcam.mp4" +
"&smokeWebcamShadow=1.5" +
"&smokeWebcamSize=0.75" +
"&smokePipelineModel=legacy" +
"&smokeBackendPreference=breeze" +
"&smokeRenderBackend=webgpu" +
"&smokeMaxEncodeQueue=8" +
"&smokeMaxDecodeQueue=9" +
"&smokeMaxPendingFrames=10" +
"&smokeProject=/tmp/project.recordly" +
"&smokeQuality=source" +
"&smokeFps=60",
);
expect(config).toEqual({
enabled: true,
inputPath: "/tmp/input.mp4",
outputPath: "/tmp/output.mp4",
useNativeExport: true,
encodingMode: "quality",
shadowIntensity: 0,
webcamInputPath: "/tmp/webcam.mp4",
webcamShadow: 1.5,
webcamSize: 0.75,
pipelineModel: "legacy",
backendPreference: "breeze",
renderBackend: "webgpu",
maxEncodeQueue: 8,
maxDecodeQueue: 9,
maxPendingFrames: 10,
projectPath: "/tmp/project.recordly",
quality: "source",
fps: 60,
});
});
it("drops invalid optional smoke export values", () => {
const config = getSmokeExportConfig(
"?smokeExport=1" +
"&smokeEncodingMode=slow" +
"&smokeShadowIntensity=-1" +
"&smokeWebcamShadow=nan" +
"&smokeWebcamSize=-0.1" +
"&smokePipelineModel=classic" +
"&smokeBackendPreference=native" +
"&smokeRenderBackend=canvas" +
"&smokeMaxEncodeQueue=0" +
"&smokeMaxDecodeQueue=-4" +
"&smokeMaxPendingFrames=abc" +
"&smokeQuality=ultra" +
"&smokeFps=25",
);
expect(config).toMatchObject({
enabled: true,
useNativeExport: false,
encodingMode: undefined,
shadowIntensity: undefined,
webcamShadow: undefined,
webcamSize: undefined,
pipelineModel: undefined,
backendPreference: undefined,
renderBackend: undefined,
maxEncodeQueue: undefined,
maxDecodeQueue: undefined,
maxPendingFrames: undefined,
quality: undefined,
fps: undefined,
});
});
});
describe("getDevOpenRecordingConfig", () => {
it("reads dev-open paths independently from smoke export", () => {
expect(
getDevOpenRecordingConfig(
"?devOpenInput=/tmp/input.mp4&devOpenWebcam=/tmp/webcam.mp4",
),
).toEqual({
inputPath: "/tmp/input.mp4",
webcamInputPath: "/tmp/webcam.mp4",
});
});
});
@@ -0,0 +1,131 @@
import {
isValidMp4FrameRate,
type ExportBackendPreference,
type ExportEncodingMode,
type ExportMp4FrameRate,
type ExportPipelineModel,
type ExportQuality,
type ExportRenderBackend,
} from "@/lib/exporter/types";
export type SmokeExportConfig = {
enabled: boolean;
inputPath: string | null;
outputPath: string | null;
useNativeExport: boolean;
encodingMode?: ExportEncodingMode;
shadowIntensity?: number;
webcamInputPath?: string | null;
webcamShadow?: number;
webcamSize?: number;
pipelineModel?: ExportPipelineModel;
backendPreference?: ExportBackendPreference;
renderBackend?: ExportRenderBackend;
maxEncodeQueue?: number;
maxDecodeQueue?: number;
maxPendingFrames?: number;
projectPath?: string | null;
quality?: ExportQuality;
fps?: ExportMp4FrameRate;
};
export type DevOpenRecordingConfig = {
inputPath: string | null;
webcamInputPath: string | null;
};
function parseSmokeExportNumber(value: string | null): number | undefined {
if (value === null) {
return undefined;
}
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
}
function parseSmokeExportNonNegativeNumber(value: string | null): number | undefined {
if (value === null) {
return undefined;
}
const parsed = Number.parseFloat(value);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
}
function parseSmokeExportQuality(value: string | null): ExportQuality | undefined {
if (value === "medium" || value === "good" || value === "high" || value === "source") {
return value;
}
return undefined;
}
function parseSmokeExportFps(value: string | null): ExportMp4FrameRate | undefined {
if (value === null) return undefined;
const parsed = Number.parseInt(value, 10);
return isValidMp4FrameRate(parsed) ? parsed : undefined;
}
function parseSmokeRenderBackend(value: string | null): ExportRenderBackend | undefined {
return value === "webgl" || value === "webgpu" ? value : undefined;
}
export function getSmokeExportConfig(search: string): SmokeExportConfig {
const params = new URLSearchParams(search);
const enabled = params.get("smokeExport") === "1";
return {
enabled,
inputPath: enabled ? params.get("smokeInput") : null,
outputPath: enabled ? params.get("smokeOutput") : null,
useNativeExport: enabled ? params.get("smokeUseNativeExport") === "1" : false,
encodingMode:
enabled && params.get("smokeEncodingMode") === "fast"
? "fast"
: enabled && params.get("smokeEncodingMode") === "balanced"
? "balanced"
: enabled && params.get("smokeEncodingMode") === "quality"
? "quality"
: undefined,
shadowIntensity: enabled
? parseSmokeExportNonNegativeNumber(params.get("smokeShadowIntensity"))
: undefined,
webcamInputPath: enabled ? params.get("smokeWebcamInput") : null,
webcamShadow: enabled
? parseSmokeExportNonNegativeNumber(params.get("smokeWebcamShadow"))
: undefined,
webcamSize: enabled
? parseSmokeExportNonNegativeNumber(params.get("smokeWebcamSize"))
: undefined,
pipelineModel:
enabled && params.get("smokePipelineModel") === "modern"
? "modern"
: enabled && params.get("smokePipelineModel") === "legacy"
? "legacy"
: undefined,
backendPreference:
enabled && params.get("smokeBackendPreference") === "auto"
? "auto"
: enabled && params.get("smokeBackendPreference") === "webcodecs"
? "webcodecs"
: enabled && params.get("smokeBackendPreference") === "breeze"
? "breeze"
: undefined,
renderBackend: enabled ? parseSmokeRenderBackend(params.get("smokeRenderBackend")) : undefined,
maxEncodeQueue: enabled ? parseSmokeExportNumber(params.get("smokeMaxEncodeQueue")) : undefined,
maxDecodeQueue: enabled ? parseSmokeExportNumber(params.get("smokeMaxDecodeQueue")) : undefined,
maxPendingFrames: enabled
? parseSmokeExportNumber(params.get("smokeMaxPendingFrames"))
: undefined,
projectPath: enabled ? params.get("smokeProject") : null,
quality: enabled ? parseSmokeExportQuality(params.get("smokeQuality")) : undefined,
fps: enabled ? parseSmokeExportFps(params.get("smokeFps")) : undefined,
};
}
export function getDevOpenRecordingConfig(search: string): DevOpenRecordingConfig {
const params = new URLSearchParams(search);
return {
inputPath: params.get("devOpenInput"),
webcamInputPath: params.get("devOpenWebcam"),
};
}