mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-26 07:45:34 +00:00
refactor(editor): extract smoke export config
This commit is contained in:
@@ -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"),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user