diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index f4c391e0..32ed313e 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -188,6 +188,19 @@ interface RendererNativeVideoMetadataProbe { audioSampleRate?: number; } +interface RendererNativeExportCapabilities { + platform: NodeJS.Platform; + nvidiaCuda: { + available: boolean; + skipReason: string | null; + hasNvidiaGpu: boolean | null; + hasWrapper: boolean; + explicitEnabled: boolean; + explicitDisabled: boolean; + userOptInRequired: boolean; + }; +} + interface Window { electronAPI: { hudOverlaySetIgnoreMouse: (ignore: boolean) => void; @@ -329,6 +342,11 @@ interface Window { metadata?: RendererNativeVideoMetadataProbe; error?: string; }>; + getNativeExportCapabilities: () => Promise<{ + success: boolean; + capabilities?: RendererNativeExportCapabilities; + error?: string; + }>; nativeStaticLayoutExport: (options: { sessionId?: string; inputPath: string; @@ -389,6 +407,7 @@ interface Window { }>; chunkDurationSec?: number; experimentalWindowsGpuCompositor?: boolean; + experimentalNvidiaCudaExport?: boolean; audioOptions?: { audioMode?: "none" | "copy-source" | "trim-source" | "edited-track"; audioSourcePath?: string | null; diff --git a/electron/ipc/export/native-video.test.ts b/electron/ipc/export/native-video.test.ts index ddd99168..1c82a8d4 100644 --- a/electron/ipc/export/native-video.test.ts +++ b/electron/ipc/export/native-video.test.ts @@ -55,6 +55,7 @@ import { buildNativeVideoAudioMuxArgs, canCopyAudioCodecIntoMp4, getExperimentalNvidiaCudaExportSkipReason, + getNativeExportCapabilities, getNativeGpuCompositorStallTimeoutMs, getNativeStaticLayoutSourceProxyBitrate, getNvidiaCudaAudioExportSkipReason, @@ -307,7 +308,7 @@ describe("getNvidiaCudaAudioExportSkipReason", () => { }); describe("getNvidiaCudaAutoStallTimeoutMs", () => { - it("only applies the stall guard to packaged auto candidates by default", () => { + it("only applies the stall guard to active validated CUDA candidates by default", () => { expect(getNvidiaCudaAutoStallTimeoutMs(false)).toBeNull(); expect(getNvidiaCudaAutoStallTimeoutMs(true)).toBe(120_000); }); @@ -396,13 +397,41 @@ describe("hasNvidiaGpuDeviceInGpuInfo", () => { }); }); +describe("getNativeExportCapabilities", () => { + it("reports NVIDIA CUDA availability when the wrapper and an NVIDIA GPU are present", async () => { + const capabilities = await withPackagedCudaCandidate( + { gpuDevice: [{ vendorId: 0x10de, deviceString: "NVIDIA GeForce GTX 1650" }] }, + () => getNativeExportCapabilities(), + ); + + expect(capabilities.nvidiaCuda.available).toBe(process.platform === "win32"); + expect(capabilities.nvidiaCuda.hasWrapper).toBe(process.platform === "win32"); + expect(capabilities.nvidiaCuda.hasNvidiaGpu).toBe(process.platform === "win32" ? true : null); + }); +}); + describe("getExperimentalNvidiaCudaExportSkipReason", () => { - it("auto-enables packaged CUDA candidates when the helper and an NVIDIA GPU are present", async () => { + it("requires user opt-in before packaged CUDA candidates run", async () => { + const reason = await withPackagedCudaCandidate( + { gpuDevice: [{ vendorId: 0x10de, deviceString: "NVIDIA GeForce GTX 1650" }] }, + () => + getExperimentalNvidiaCudaExportSkipReason( + createNvidiaCudaSkipOptions({ + audioOptions: { audioMode: "copy-source", audioSourcePath: "input.mp4" }, + }), + ), + ); + + expect(reason).toBe(process.platform === "win32" ? "env-disabled" : "not-windows"); + }); + + it("allows user opt-in CUDA candidates when the helper and an NVIDIA GPU are present", async () => { const reason = await withPackagedCudaCandidate( { gpuDevice: [{ vendorId: 0x10de, deviceString: "NVIDIA GeForce GTX 1650" }] }, () => getExperimentalNvidiaCudaExportSkipReason( createNvidiaCudaSkipOptions({ + experimentalNvidiaCudaExport: true, audioOptions: { audioMode: "copy-source", audioSourcePath: "input.mp4" }, }), ), @@ -492,10 +521,13 @@ describe("getExperimentalNvidiaCudaExportSkipReason", () => { } }); - it("skips packaged CUDA auto-candidates when Electron reports no NVIDIA GPU", async () => { + it("skips user opt-in CUDA candidates when Electron reports no NVIDIA GPU", async () => { const reason = await withPackagedCudaCandidate( { gpuDevice: [{ vendorId: 0x8086, deviceString: "Intel UHD Graphics" }] }, - () => getExperimentalNvidiaCudaExportSkipReason(createNvidiaCudaSkipOptions()), + () => + getExperimentalNvidiaCudaExportSkipReason( + createNvidiaCudaSkipOptions({ experimentalNvidiaCudaExport: true }), + ), ); expect(reason).toBe( @@ -503,12 +535,14 @@ describe("getExperimentalNvidiaCudaExportSkipReason", () => { ); }); - it("lets the packaged auto-candidate be explicitly disabled", async () => { + it("lets the user opt-in candidate be explicitly disabled", async () => { const reason = await withPackagedCudaCandidate( { gpuDevice: [{ vendorId: 0x10de, deviceString: "NVIDIA GeForce GTX 1650" }] }, async () => { process.env.RECORDLY_EXPERIMENTAL_NVIDIA_CUDA_EXPORT = "0"; - return getExperimentalNvidiaCudaExportSkipReason(createNvidiaCudaSkipOptions()); + return getExperimentalNvidiaCudaExportSkipReason( + createNvidiaCudaSkipOptions({ experimentalNvidiaCudaExport: true }), + ); }, ); diff --git a/electron/ipc/export/native-video.ts b/electron/ipc/export/native-video.ts index 27ee4c52..ad884651 100644 --- a/electron/ipc/export/native-video.ts +++ b/electron/ipc/export/native-video.ts @@ -151,6 +151,7 @@ export interface NativeStaticLayoutExportOptions { timelineMapPath?: string | null; chunkDurationSec?: number; experimentalWindowsGpuCompositor?: boolean; + experimentalNvidiaCudaExport?: boolean; audioOptions?: NativeVideoExportFinishOptions; nvidiaCudaForceVideoOnly?: boolean; } @@ -923,6 +924,19 @@ function setNativeStaticLayoutExportProcessPriority(pid: number | undefined, lab export const nativeStaticLayoutExportSessions = new Map(); +export interface NativeExportCapabilities { + platform: NodeJS.Platform; + nvidiaCuda: { + available: boolean; + skipReason: string | null; + hasNvidiaGpu: boolean | null; + hasWrapper: boolean; + explicitEnabled: boolean; + explicitDisabled: boolean; + userOptInRequired: boolean; + }; +} + export function parseFfmpegDurationSeconds(value: string): number | null { const parts = value.trim().split(":"); if (parts.length !== 3) { @@ -1856,13 +1870,18 @@ export function hasNvidiaGpuDeviceInGpuInfo(gpuInfo: unknown) { } async function hasNvidiaGpuForCudaExportCandidate() { + const hasNvidiaGpu = await probeNvidiaGpuForCudaExportCandidate(); + return hasNvidiaGpu ?? true; +} + +async function probeNvidiaGpuForCudaExportCandidate(): Promise { const getGPUInfo = ( app as typeof app & { getGPUInfo?: (infoType: "basic") => Promise; } ).getGPUInfo; if (typeof getGPUInfo !== "function") { - return true; + return null; } try { @@ -1872,7 +1891,7 @@ async function hasNvidiaGpuForCudaExportCandidate() { "[native-static-layout-export] Unable to inspect GPU info before NVIDIA CUDA export; letting the helper decide", error, ); - return true; + return null; } } @@ -1959,12 +1978,12 @@ function isExplicitNvidiaCudaExportDisabled() { return process.env[NVIDIA_CUDA_EXPORT_ENV] === "0"; } -function isPackagedNvidiaCudaExportAutoCandidateEnabled() { - return app.isPackaged && !isExplicitNvidiaCudaExportDisabled(); +function isUserOptedInNvidiaCudaExport(options: NativeStaticLayoutExportOptions) { + return options.experimentalNvidiaCudaExport === true && !isExplicitNvidiaCudaExportDisabled(); } -function isPackagedNvidiaCudaExportAutoCandidateActive() { - return isPackagedNvidiaCudaExportAutoCandidateEnabled() && !isExplicitNvidiaCudaExportEnabled(); +function isValidatedNvidiaCudaFallbackCandidate(options: NativeStaticLayoutExportOptions) { + return isUserOptedInNvidiaCudaExport(options) && !isExplicitNvidiaCudaExportEnabled(); } function isNvidiaCudaForceVideoOnlyEnabled() { @@ -1972,7 +1991,7 @@ function isNvidiaCudaForceVideoOnlyEnabled() { } export function getNvidiaCudaAutoStallTimeoutMs( - autoCandidateActive = isPackagedNvidiaCudaExportAutoCandidateActive(), + autoCandidateActive = false, ) { if (!autoCandidateActive && !isExplicitNvidiaCudaExportEnabled()) { return null; @@ -2011,17 +2030,19 @@ export async function getExperimentalNvidiaCudaExportSkipReason( if (process.platform !== "win32") { return "not-windows"; } + if (isExplicitNvidiaCudaExportDisabled()) { + return "env-disabled"; + } const explicitCuda = isExplicitNvidiaCudaExportEnabled(); - const packagedAutoCandidateEnabled = isPackagedNvidiaCudaExportAutoCandidateEnabled(); - const packagedAutoCandidateActive = isPackagedNvidiaCudaExportAutoCandidateActive(); - if (!explicitCuda && !packagedAutoCandidateEnabled) { + const userOptIn = isUserOptedInNvidiaCudaExport(options); + if (!explicitCuda && !userOptIn) { return "env-disabled"; } if (!options.experimentalWindowsGpuCompositor) { return "windows-gpu-compositor-disabled"; } - if (packagedAutoCandidateActive) { + if (userOptIn) { if (!(await resolveExperimentalNvidiaCudaExportScriptPath())) { return "cuda-wrapper-unavailable"; } @@ -2032,10 +2053,54 @@ export async function getExperimentalNvidiaCudaExportSkipReason( return getNvidiaCudaAudioExportSkipReason(options.audioOptions?.audioMode, { allowValidatedFallbackCandidate: - packagedAutoCandidateActive || isNvidiaCudaForceVideoOnlyEnabled(), + isValidatedNvidiaCudaFallbackCandidate(options) || isNvidiaCudaForceVideoOnlyEnabled(), }); } +export async function getNativeExportCapabilities(): Promise { + if (process.platform !== "win32") { + return { + platform: process.platform, + nvidiaCuda: { + available: false, + skipReason: "not-windows", + hasNvidiaGpu: null, + hasWrapper: false, + explicitEnabled: isExplicitNvidiaCudaExportEnabled(), + explicitDisabled: isExplicitNvidiaCudaExportDisabled(), + userOptInRequired: true, + }, + }; + } + + const explicitEnabled = isExplicitNvidiaCudaExportEnabled(); + const explicitDisabled = isExplicitNvidiaCudaExportDisabled(); + const wrapperPath = await resolveExperimentalNvidiaCudaExportScriptPath(); + const hasNvidiaGpu = await probeNvidiaGpuForCudaExportCandidate(); + const skipReason = explicitDisabled + ? "env-disabled" + : !wrapperPath + ? "cuda-wrapper-unavailable" + : hasNvidiaGpu === false + ? "nvidia-gpu-unavailable" + : hasNvidiaGpu === null + ? "nvidia-gpu-probe-unavailable" + : null; + + return { + platform: process.platform, + nvidiaCuda: { + available: skipReason === null, + skipReason, + hasNvidiaGpu, + hasWrapper: Boolean(wrapperPath), + explicitEnabled, + explicitDisabled, + userOptInRequired: !explicitEnabled, + }, + }; +} + export async function resolveExperimentalNvidiaCudaExportScriptPath() { if (process.platform !== "win32") { return null; @@ -2750,7 +2815,9 @@ async function runExperimentalNvidiaCudaStaticLayoutExport( const startedAt = getNowMs(); const startedAtIso = new Date().toISOString(); const timeoutMs = Math.max(20 * 60 * 1000, options.durationSec * 2000); - const stallTimeoutMs = getNvidiaCudaAutoStallTimeoutMs(); + const stallTimeoutMs = getNvidiaCudaAutoStallTimeoutMs( + isValidatedNvidiaCudaFallbackCandidate(options), + ); const ffmpegDirectory = path.dirname(ffmpegPath); const pathKey = process.platform === "win32" ? "Path" : "PATH"; const env = { @@ -3307,10 +3374,11 @@ export async function exportNativeStaticLayoutVideo( const nvidiaCudaSkipReason = await getExperimentalNvidiaCudaExportSkipReason(options); let shouldTryNvidiaCuda = nvidiaCudaSkipReason === null; + const validatedCudaFallbackCandidate = + isValidatedNvidiaCudaFallbackCandidate(options); if ( shouldTryNvidiaCuda && - (isPackagedNvidiaCudaExportAutoCandidateActive() || - isNvidiaCudaForceVideoOnlyEnabled()) && + (validatedCudaFallbackCandidate || isNvidiaCudaForceVideoOnlyEnabled()) && (experimentalNvidiaCudaOptions.audioOptions?.audioMode ?? "none") !== "none" ) { experimentalNvidiaCudaOptions = { @@ -3323,13 +3391,13 @@ export async function exportNativeStaticLayoutVideo( audioMode: experimentalNvidiaCudaOptions.audioOptions?.audioMode ?? "none", forcedByEnv: isNvidiaCudaForceVideoOnlyEnabled(), - packagedAutoCandidate: isPackagedNvidiaCudaExportAutoCandidateActive(), + userOptIn: options.experimentalNvidiaCudaExport === true, }, ); } const shouldLogNvidiaCudaSkip = isExplicitNvidiaCudaExportEnabled() || - (isPackagedNvidiaCudaExportAutoCandidateEnabled() && + (options.experimentalNvidiaCudaExport === true && nvidiaCudaSkipReason !== "env-disabled"); if ( !shouldTryNvidiaCuda && @@ -3342,7 +3410,7 @@ export async function exportNativeStaticLayoutVideo( reason: nvidiaCudaSkipReason, audioMode: options.audioOptions?.audioMode ?? "none", overrideEnv: NVIDIA_CUDA_ALLOW_AUDIO_EXPORT_ENV, - packagedAutoCandidate: isPackagedNvidiaCudaExportAutoCandidateEnabled(), + userOptIn: options.experimentalNvidiaCudaExport === true, }, ); } diff --git a/electron/ipc/register/export.ts b/electron/ipc/register/export.ts index ede2272f..78afeb2f 100644 --- a/electron/ipc/register/export.ts +++ b/electron/ipc/register/export.ts @@ -18,6 +18,7 @@ import { enqueueNativeVideoExportFrameWrites, exportNativeStaticLayoutVideo, flushNativeVideoExportPendingWriteRequests, + getNativeExportCapabilities, getNativeVideoExportMaxQueuedWriteBytes, getNativeVideoExportSessionError, isHardwareAcceleratedVideoEncoder, @@ -413,6 +414,21 @@ export function registerExportHandlers() { } }); + ipcMain.handle("get-native-export-capabilities", async () => { + try { + return { + success: true, + capabilities: await getNativeExportCapabilities(), + }; + } catch (error) { + console.warn("[native-export-capabilities] Failed:", error); + return { + success: false, + error: error instanceof Error ? error.message : String(error), + }; + } + }); + ipcMain.handle( "native-static-layout-export", async (event, options: NativeStaticLayoutExportOptions) => { diff --git a/electron/preload.ts b/electron/preload.ts index 8ef19765..932db07b 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -90,6 +90,18 @@ type NativeVideoMetadataProbe = { audioCodec?: string; audioSampleRate?: number; }; +type NativeExportCapabilities = { + platform: NodeJS.Platform; + nvidiaCuda: { + available: boolean; + skipReason: string | null; + hasNvidiaGpu: boolean | null; + hasWrapper: boolean; + explicitEnabled: boolean; + explicitDisabled: boolean; + userOptInRequired: boolean; + }; +}; const nativeVideoExportWriteRequests = new Map< number, @@ -195,6 +207,13 @@ contextBridge.exposeInMainWorld("electronAPI", { error?: string; }>; }, + getNativeExportCapabilities: () => { + return ipcRenderer.invoke("get-native-export-capabilities") as Promise<{ + success: boolean; + capabilities?: NativeExportCapabilities; + error?: string; + }>; + }, nativeStaticLayoutExport: (options: { sessionId?: string; inputPath: string; @@ -255,6 +274,7 @@ contextBridge.exposeInMainWorld("electronAPI", { }>; chunkDurationSec?: number; experimentalWindowsGpuCompositor?: boolean; + experimentalNvidiaCudaExport?: boolean; audioOptions?: { audioMode?: "none" | "copy-source" | "trim-source" | "edited-track"; audioSourcePath?: string | null; diff --git a/src/components/video-editor/ExportSettingsMenu.tsx b/src/components/video-editor/ExportSettingsMenu.tsx index 08333bbd..4d690a0a 100644 --- a/src/components/video-editor/ExportSettingsMenu.tsx +++ b/src/components/video-editor/ExportSettingsMenu.tsx @@ -26,6 +26,9 @@ interface ExportSettingsMenuProps { onMp4FrameRateChange?: (frameRate: ExportMp4FrameRate) => void; exportPipelineModel?: ExportPipelineModel; onExportPipelineModelChange?: (pipelineModel: ExportPipelineModel) => void; + experimentalNvidiaCudaExport?: boolean; + onExperimentalNvidiaCudaExportChange?: (enabled: boolean) => void; + nvidiaCudaExportAvailable?: boolean; mp4OutputDimensions?: Record; gifFrameRate: GifFrameRate; onGifFrameRateChange?: (rate: GifFrameRate) => void; @@ -49,6 +52,9 @@ export function ExportSettingsMenu({ onMp4FrameRateChange, exportPipelineModel = "modern", onExportPipelineModelChange, + experimentalNvidiaCudaExport = false, + onExperimentalNvidiaCudaExportChange, + nvidiaCudaExportAvailable = false, mp4OutputDimensions, gifFrameRate, onGifFrameRateChange, @@ -330,6 +336,35 @@ export function ExportSettingsMenu({ "Lightning (Beta) automatically uses the fastest compatible backend and falls back when needed.", )}

+ {!isLegacyModel && nvidiaCudaExportAvailable ? ( +
+
+
+ + {tSettings("export.nvidiaCuda.title", "NVIDIA CUDA")} + + + {tSettings("export.nvidiaCuda.badge", "Experimental")} + +
+

+ {tSettings( + "export.nvidiaCuda.hint", + "Try GPU export on this Windows device.", + )} +

+
+ +
+ ) : null} ) : (
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 0a59ea7a..6308efc6 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -52,6 +52,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover import { Toaster } from "@/components/ui/sonner"; import { useI18n } from "@/contexts/I18nContext"; import { useShortcuts } from "@/contexts/ShortcutsContext"; +import { loadAppSetting, saveAppSetting } from "@/lib/appSettings"; import { calculateOutputDimensions, DEFAULT_MP4_CODEC, @@ -108,6 +109,8 @@ const PhSettings = (props: { className?: string; weight?: "fill" | "regular" }) ); +const NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY = "recordly.export.experimentalNvidiaCuda"; + import type { SourceAudioTrackSettings } from "@/components/video-editor/audio/audioTypes"; import { extensionHost } from "@/lib/extensions"; import { useVideoEditorAudio } from "./audio/useVideoEditorAudio"; @@ -551,6 +554,10 @@ export default function VideoEditor() { const [exportPipelineModel, setExportPipelineModel] = useState( initialEditorPreferences.exportPipelineModel, ); + const [nvidiaCudaExportAvailable, setNvidiaCudaExportAvailable] = useState(false); + const [experimentalNvidiaCudaExport, setExperimentalNvidiaCudaExportState] = useState( + () => loadAppSetting(NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY) === true, + ); const [mp4FrameRate, setMp4FrameRate] = useState( initialEditorPreferences.mp4FrameRate ?? DEFAULT_MP4_EXPORT_FRAME_RATE, ); @@ -621,6 +628,48 @@ export default function VideoEditor() { }); }, []); + useEffect(() => { + let cancelled = false; + + void (async () => { + try { + const result = await window.electronAPI?.getNativeExportCapabilities?.(); + if (cancelled) { + return; + } + + const available = result?.capabilities?.nvidiaCuda.available === true; + setNvidiaCudaExportAvailable(available); + if (!available) { + setExperimentalNvidiaCudaExportState(false); + } + } catch (error) { + if (cancelled) { + return; + } + console.warn("[export] Failed to load native export capabilities", error); + setNvidiaCudaExportAvailable(false); + setExperimentalNvidiaCudaExportState(false); + } + })(); + + return () => { + cancelled = true; + }; + }, []); + + const setExperimentalNvidiaCudaExport = useCallback( + (enabled: boolean) => { + const nextEnabled = Boolean(enabled && nvidiaCudaExportAvailable); + setExperimentalNvidiaCudaExportState(nextEnabled); + saveAppSetting(NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY, nextEnabled); + if (nextEnabled) { + setExportPipelineModel("modern"); + } + }, + [nvidiaCudaExportAvailable], + ); + useEffect(() => { autoSuggestedVideoPathRef.current = null; pendingFreshRecordingAutoSuggestTelemetryCountRef.current = 0; @@ -4130,6 +4179,10 @@ export default function VideoEditor() { const useExperimentalNativeExport = pipelineModel === "modern" && (smokeExportConfig.enabled ? smokeExportConfig.useNativeExport : true); + const useExperimentalNvidiaCudaExport = + useExperimentalNativeExport && + experimentalNvidiaCudaExport && + nvidiaCudaExportAvailable; const backendPreference = pipelineModel === "legacy" ? "webcodecs" @@ -4171,6 +4224,7 @@ export default function VideoEditor() { preferredEncoderPath: supportedSourceDimensions.encoderPath, preferredRenderBackend: smokeExportConfig.renderBackend, experimentalNativeExport: useExperimentalNativeExport, + experimentalNvidiaCudaExport: useExperimentalNvidiaCudaExport, maxEncodeQueue: smokeExportConfig.maxEncodeQueue, maxDecodeQueue: smokeExportConfig.maxDecodeQueue, maxPendingFrames: smokeExportConfig.maxPendingFrames, @@ -4483,6 +4537,8 @@ export default function VideoEditor() { exportEncodingMode, exportBackendPreference, exportPipelineModel, + experimentalNvidiaCudaExport, + nvidiaCudaExportAvailable, borderRadius, padding, cropRegion, @@ -5425,6 +5481,13 @@ export default function VideoEditor() { onMp4FrameRateChange={setMp4FrameRate} exportPipelineModel={exportPipelineModel} onExportPipelineModelChange={setExportPipelineModel} + experimentalNvidiaCudaExport={ + experimentalNvidiaCudaExport && nvidiaCudaExportAvailable + } + onExperimentalNvidiaCudaExportChange={ + setExperimentalNvidiaCudaExport + } + nvidiaCudaExportAvailable={nvidiaCudaExportAvailable} exportQuality={exportQuality} onExportQualityChange={setExportQuality} gifFrameRate={gifFrameRate} diff --git a/src/lib/exporter/modernVideoExporter.ts b/src/lib/exporter/modernVideoExporter.ts index cb192593..c1f01a54 100644 --- a/src/lib/exporter/modernVideoExporter.ts +++ b/src/lib/exporter/modernVideoExporter.ts @@ -2512,6 +2512,8 @@ export class ModernVideoExporter { timelineSegments, chunkDurationSec: STATIC_LAYOUT_CHUNK_DURATION_SEC, experimentalWindowsGpuCompositor: this.config.experimentalNativeExport === true, + experimentalNvidiaCudaExport: + this.config.experimentalNvidiaCudaExport === true, audioOptions: { ...audioOptions, outputDurationSec: effectiveDuration, diff --git a/src/lib/exporter/types.ts b/src/lib/exporter/types.ts index e0eb7934..72682f01 100644 --- a/src/lib/exporter/types.ts +++ b/src/lib/exporter/types.ts @@ -8,6 +8,7 @@ export interface ExportConfig { backendPreference?: ExportBackendPreference; preferredRenderBackend?: ExportRenderBackend; experimentalNativeExport?: boolean; + experimentalNvidiaCudaExport?: boolean; maxEncodeQueue?: number; maxDecodeQueue?: number; maxPendingFrames?: number;