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;