From f8122bde18bcf49949cd88d951b50636200a5e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E5=BD=AA?= Date: Mon, 20 Apr 2026 21:58:52 +0800 Subject: [PATCH] chore(smoke-export): let the smoke harness open .recordly project files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the smoke-export harness only accepted a raw MP4 input path. Validating the export pipeline on a real editor project (with its own zoom regions, wallpaper, annotations, and webcam state) required opening the editor by hand and clicking through the GUI. This adds three new environment variables that are picked up by the existing smoke-export query string plumbing: - RECORDLY_SMOKE_EXPORT_PROJECT — path to a .recordly project file; when set, the editor opens it via openProjectFileAtPath and applies the saved state before the auto-export fires. - RECORDLY_SMOKE_EXPORT_QUALITY — overrides the hard-coded "good" quality used by the auto-export trigger; accepts medium / good / high / source. - RECORDLY_SMOKE_EXPORT_FPS — overrides the frame rate; accepts 24 / 30 / 60. No behavior change for existing RECORDLY_SMOKE_EXPORT_INPUT runs; the new inputs are strictly additive and optional. The startup log line prefers the project path over the raw-input path when both are populated. --- electron/main.ts | 8 +-- electron/windows.ts | 9 ++++ src/components/video-editor/VideoEditor.tsx | 55 ++++++++++++++++++++- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index 9d195a9d..c792ba3e 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -882,9 +882,11 @@ app.whenReady().then(async () => { if (IS_SMOKE_EXPORT) { await logSmokeExportGpuDiagnostics(); - console.log( - `[smoke-export] Starting editor smoke export for ${process.env.RECORDLY_SMOKE_EXPORT_INPUT ?? ""}`, - ); + const smokeSource = + process.env.RECORDLY_SMOKE_EXPORT_PROJECT ?? + process.env.RECORDLY_SMOKE_EXPORT_INPUT ?? + ""; + console.log(`[smoke-export] Starting editor smoke export for ${smokeSource}`); createEditorWindowWrapper(); return; } diff --git a/electron/windows.ts b/electron/windows.ts index be618617..5ab6675a 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -89,6 +89,15 @@ function getEditorWindowQuery(): Record { if (process.env.RECORDLY_SMOKE_EXPORT_MAX_PENDING_FRAMES) { query.smokeMaxPendingFrames = process.env.RECORDLY_SMOKE_EXPORT_MAX_PENDING_FRAMES; } + if (process.env.RECORDLY_SMOKE_EXPORT_PROJECT) { + query.smokeProject = process.env.RECORDLY_SMOKE_EXPORT_PROJECT; + } + if (process.env.RECORDLY_SMOKE_EXPORT_QUALITY) { + query.smokeQuality = process.env.RECORDLY_SMOKE_EXPORT_QUALITY; + } + if (process.env.RECORDLY_SMOKE_EXPORT_FPS) { + query.smokeFps = process.env.RECORDLY_SMOKE_EXPORT_FPS; + } } return query; diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 9d0f4ece..919ed14e 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -57,6 +57,7 @@ import { GifExporter, type GifFrameRate, type GifSizePreset, + isValidMp4FrameRate, ModernVideoExporter, probeSupportedMp4Dimensions, type SupportedMp4Dimensions, @@ -219,6 +220,9 @@ type SmokeExportConfig = { maxEncodeQueue?: number; maxDecodeQueue?: number; maxPendingFrames?: number; + projectPath?: string | null; + quality?: ExportQuality; + fps?: ExportMp4FrameRate; }; async function writeSmokeExportReport( @@ -290,6 +294,19 @@ function parseSmokeExportNonNegativeNumber(value: string | null): number | undef 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 getSmokeExportConfig(search: string): SmokeExportConfig { const params = new URLSearchParams(search); const enabled = params.get("smokeExport") === "1"; @@ -340,6 +357,9 @@ function getSmokeExportConfig(search: string): SmokeExportConfig { 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, }; } @@ -1771,6 +1791,32 @@ export default function VideoEditor() { useEffect(() => { async function loadInitialData() { try { + if (smokeExportConfig.enabled && smokeExportConfig.projectPath) { + const projectResult = await window.electronAPI.openProjectFileAtPath( + smokeExportConfig.projectPath, + ); + if (!projectResult.success || !projectResult.project) { + setError( + `Smoke export failed to load project ${smokeExportConfig.projectPath}: ${ + projectResult.error || projectResult.message || "unknown error" + }`, + ); + return; + } + const restored = await applyLoadedProject( + projectResult.project, + projectResult.path ?? smokeExportConfig.projectPath, + ); + if (!restored) { + setError( + `Smoke export could not apply project ${smokeExportConfig.projectPath}`, + ); + return; + } + setError(null); + return; + } + if (smokeExportConfig.enabled) { if (!smokeExportConfig.inputPath) { setError("Smoke export input path is missing."); @@ -1884,6 +1930,7 @@ export default function VideoEditor() { initialEditorPreferences, smokeExportConfig.enabled, smokeExportConfig.inputPath, + smokeExportConfig.projectPath, smokeExportConfig.webcamInputPath, smokeExportConfig.webcamShadow, smokeExportConfig.webcamSize, @@ -3949,13 +3996,17 @@ export default function VideoEditor() { } } else { // MP4 Export - const quality = settings.quality ?? exportQuality; + const quality = smokeExportConfig.enabled + ? (smokeExportConfig.quality ?? settings.quality ?? exportQuality) + : (settings.quality ?? exportQuality); const encodingMode = smokeExportConfig.enabled ? (smokeExportConfig.encodingMode ?? settings.encodingMode ?? exportEncodingMode) : (settings.encodingMode ?? exportEncodingMode); - const selectedMp4FrameRate = settings.mp4FrameRate ?? mp4FrameRate; + const selectedMp4FrameRate = smokeExportConfig.enabled + ? (smokeExportConfig.fps ?? settings.mp4FrameRate ?? mp4FrameRate) + : (settings.mp4FrameRate ?? mp4FrameRate); const pipelineModel = smokeExportConfig.enabled ? (smokeExportConfig.pipelineModel ?? (smokeExportConfig.useNativeExport ? "modern" : "legacy"))